diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile
index 1be4c04439..e3dfb53c1d 100644
--- a/ext/jni/GNUmakefile
+++ b/ext/jni/GNUmakefile
@@ -68,6 +68,7 @@ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/%,\
ResultCode.java \
RollbackHook.java \
SQLFunction.java \
+ SQLLog.java \
sqlite3_context.java \
sqlite3.java \
SQLite3Jni.java \
@@ -160,17 +161,16 @@ 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=1 \
-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_DEBUG
SQLITE_OPT += -g -DDEBUG -UNDEBUG
@@ -226,12 +226,16 @@ $(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-one: $(SQLite3Jni.class) $(sqlite3-jni.dll)
+ $(bin.java) $(test.main.flags) $(test.flags)
+test: test-one
+ @echo "Again in multi-threaded mode:";
+ $(bin.java) $(test.main.flags) -t 5 -r 20 -shuffle $(test.flags)
tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test))
tester.flags ?= # --verbose
@@ -272,8 +276,7 @@ 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)
@@ -317,6 +320,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 cb51a21cd3..6e5e07cc03 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:
@@ -41,13 +44,34 @@ Non-goals:
them off of the C-style API.
-Significant TODOs
-========================================================================
+Hello World
+-----------------------------------------------------------------------
-- 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.
+```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 +84,108 @@ 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.
+
+
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.
+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.
-Golden Rule: _Never_ Throw from Callbacks
+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.
+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.
-UTF-8(-ish)
+
+Golden Rule #2: _Never_ Throw from Callbacks (Unless...)
------------------------------------------------------------------------
-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).
+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`.
-Known consequences and limitations of this discrepancy include:
+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.
-- 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.
+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.
-[modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8
+
+Awkward Callback Names
+------------------------------------------------------------------------
+
+In places where the Java interface uses callbacks (see below), those
+callbacks often have what might fairly be labeled as awkward names,
+e.g. `sqlScalarFunction.xFunc()` and `preupdateHook.xPreUpdate()`.
+Those names were chosen because they match the corresponding arguments
+in the C-level API docs. If they were renamed to be more Java-esque,
+documentation transparency would suffer.
Unwieldy Constructs are Re-mapped
@@ -126,7 +203,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 +222,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,7 +237,7 @@ 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);
```
@@ -169,7 +246,7 @@ Where the `Collation` class has an abstract `xCompare()` method and
no-op `xDestroy()` method which can be overridden if needed, leading to
a much more Java-esque usage:
-```
+```java
int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new Collation(){
// Required comparison function:
@@ -188,8 +265,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 +277,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 +285,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 +309,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 23a26e4a87..7596c99f3f 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 b28ea71144..7c4aa7c28b 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
@@ -105,7 +102,7 @@
# define SQLITE_TEMP_STORE 2
#endif
#ifndef SQLITE_THREADSAFE
-# define SQLITE_THREADSAFE 0
+# define SQLITE_THREADSAFE 1
#endif
/**********************************************************************/
@@ -133,9 +130,13 @@
#undef INC__STRINGIFY
#undef SQLITE_C
+/*
+** End of the sqlite3 lib setup. What follows is JNI-specific.
+*/
+
#include "sqlite3-jni.h"
-#include /* only for testing/debugging */
#include
+#include /* only for testing/debugging */
/* Only for debugging */
#define MARKER(pfexp) \
@@ -151,28 +152,30 @@
#define JDECL(ReturnType,Suffix) \
JNIEXPORT ReturnType JNICALL \
JFuncName(Suffix)
-/**
- Shortcuts for the first 2 parameters to all JNI bindings.
-
- The type of the jSelf arg differs, but no docs seem to mention
- this: for static methods it's of type jclass and for non-static
- it's jobject. jobject actually works for all funcs, in the sense
- that it compiles and runs so long as we don't use jSelf (which is
- only rarely needed in this code), but to be pedantically correct we
- need the proper type in the signature.
-
- Not even the official docs mention this discrepancy:
-
- https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers
+/*
+** 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. */
+/*
+** 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_IGNORE IFTHREW EXCEPTION_CLEAR
+#define EXCEPTION_WARN_IGNORE \
+ IFTHREW {EXCEPTION_REPORT; EXCEPTION_CLEAR;}(void)0
#define EXCEPTION_WARN_CALLBACK_THREW(STR) \
MARKER(("WARNING: " STR " MUST NOT THROW.\n")); \
(*env)->ExceptionDescribe(env)
@@ -189,10 +192,10 @@
/** 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)
+#define PtrGet_sqlite3(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.sqlite3)
+#define PtrGet_sqlite3_stmt(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.sqlite3_stmt)
+#define PtrGet_sqlite3_value(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.sqlite3_value)
+#define PtrGet_sqlite3_context(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.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;
@@ -209,47 +212,59 @@ static inline void delete_local_ref(JNIEnv * const env, jobject const v){
#define UNREF_L(VAR) delete_local_ref(env,(VAR))
/**
- Constant string class names used as keys for S3JniGlobal_nph_cache(),
-S3Jni
- and
- friends.
+ Keys for use with S3JniGlobal_nph_cache().
+*/
+typedef struct S3NphRef S3NphRef;
+struct S3NphRef {
+ const int index /* index into S3JniGlobal.nph[] */;
+ const char * const zName /* Full Java name of the class */;
+};
+
+/**
+ Keys for each concrete NativePointerHolder subclass. These are to
+ be used with S3JniGlobal_nph_cache() and friends. These are
+ initialized on-demand by S3JniGlobal_nph_cache().
*/
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;
+ const S3NphRef sqlite3;
+ const S3NphRef sqlite3_stmt;
+ const S3NphRef sqlite3_context;
+ const S3NphRef sqlite3_value;
+ const S3NphRef OutputPointer_Int32;
+ const S3NphRef OutputPointer_Int64;
+ const S3NphRef OutputPointer_sqlite3;
+ const S3NphRef OutputPointer_sqlite3_stmt;
+ const S3NphRef OutputPointer_sqlite3_value;
#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;
+ const S3NphRef OutputPointer_String;
+ const S3NphRef OutputPointer_ByteArray;
+ const S3NphRef Fts5Context;
+ const S3NphRef Fts5ExtensionApi;
+ const S3NphRef fts5_api;
+ const S3NphRef fts5_tokenizer;
+ const S3NphRef 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",
+} S3NphRefs = {
+#define NREF(INDEX, JAVANAME) { INDEX, "org/sqlite/jni/" JAVANAME }
+ NREF(0, "sqlite3"),
+ NREF(1, "sqlite3_stmt"),
+ NREF(2, "sqlite3_context"),
+ NREF(3, "sqlite3_value"),
+ NREF(4, "OutputPointer$Int32"),
+ NREF(5, "OutputPointer$Int64"),
+ NREF(6, "OutputPointer$sqlite3"),
+ NREF(7, "OutputPointer$sqlite3_stmt"),
+ NREF(8, "OutputPointer$sqlite3_value"),
#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"
+ NREF(9, "OutputPointer$String"),
+ NREF(10, "OutputPointer$ByteArray"),
+ NREF(11, "Fts5Context"),
+ NREF(12, "Fts5ExtensionApi"),
+ NREF(13, "fts5_api"),
+ NREF(14, "fts5_tokenizer"),
+ NREF(15, "Fts5Tokenizer")
#endif
+#undef NREF
};
/** Create a trivial JNI wrapper for (int CName(void)). */
@@ -264,9 +279,11 @@ static const struct {
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. */
+/*
+** 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() ); \
@@ -284,9 +301,11 @@ static const struct {
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)); \
+#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \
+ JDECL(jstring,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint ndx){ \
+ return s3jni_utf8_to_jstring(env, \
+ CName(PtrGet_sqlite3_stmt(pStmt), (int)ndx), \
+ -1); \
}
/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */
#define WRAP_INT_DB(JniNameSuffix,CName) \
@@ -305,65 +324,190 @@ static const struct {
}
/* Helpers for jstring and jbyteArray. */
-#define JSTR_TOC(ARG) (*env)->GetStringUTFChars(env, ARG, NULL)
-#define JSTR_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseStringUTFChars(env, ARG, VAR)
-#define JBA_TOC(ARG) (*env)->GetByteArrayElements(env,ARG, NULL)
-#define JBA_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT)
-
-/* Marker for code which needs(?) to be made thread-safe. REASON is a
- terse reminder about why that function requires a mutex.
-*/
-#define FIXME_THREADING(REASON)
+#define s3jni_jstring_to_mutf8(ARG) (*env)->GetStringUTFChars(env, ARG, NULL)
+#define s3jni_mutf8_release(ARG,VAR) if(VAR) (*env)->ReleaseStringUTFChars(env, ARG, VAR)
+#define s3jni_jbytearray_bytes(ARG) (*env)->GetByteArrayElements(env,ARG, NULL)
+#define s3jni_jbytearray_release(ARG,VAR) if(VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT)
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.
+ /*
+ ** 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 S3NphRefs object. The
+ ** index field of those entries are the keys for this particular
+ ** cache.
*/
- NphCache_SIZE = sizeof(S3JniClassNames) / sizeof(char const *)
+ NphCache_SIZE = sizeof(S3NphRefs) / sizeof(S3NphRef)
};
-/**
- Cache entry for NativePointerHolder lookups.
+/*
+** Cache entry for NativePointerHolder subclasses and OutputPointer
+** types.
*/
-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 S3JniNphClass S3JniNphClass;
+struct S3JniNphClass {
+ volatile const S3NphRef * pRef /* Entry from S3NphRefs. */;
+ 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. */;
};
-/**
- Cache for per-JNIEnv data.
+/** State for various hook callbacks. */
+typedef struct S3JniHook S3JniHook;
+struct S3JniHook{
+ jobject jObj /* global ref to Java instance */;
+ jmethodID midCallback /* callback method. Signature depends on
+ ** jObj's type */;
+};
- 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.
+/*
+** 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 S3JniEnvCache S3JniEnvCache;
-struct S3JniEnvCache {
+typedef struct S3JniDb S3JniDb;
+struct S3JniDb {
+ sqlite3 *pDb /* The associated db handle */;
+ jobject jDb /* A global ref of the output object which gets
+ returned from sqlite3_open(_v2)(). We need this in
+ order to have an object to pass to routines like
+ sqlite3_collation_needed()'s callback, or else we
+ have to dynamically create one for that purpose,
+ which would be fine except that it would be a
+ different instance (and maybe even a different
+ class) than the one the user may expect to
+ receive. */;
+ char * zMainDbName /* Holds the string allocated on behalf of
+ SQLITE_DBCONFIG_MAINDBNAME. */;
+ struct {
+ S3JniHook busyHandler;
+ S3JniHook 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
+ S3JniDb * pNext /* Next entry in the available/free list */;
+ S3JniDb * pPrev /* Previous entry in the available/free list */;
+};
+
+/*
+** Cache for per-JNIEnv (i.e. per-thread) data.
+*/
+typedef struct S3JniEnv S3JniEnv;
+struct S3JniEnv {
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.
+ /*
+ ** pdbOpening is used to coordinate the Java/DB connection of a
+ ** being-open()'d db in the face of auto-extensions. "The problem"
+ ** is that auto-extensions run before we can bind the C db to its
+ ** Java representation, but auto-extensions require that binding. 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.
+*/
+typedef struct S3JniAutoExtension S3JniAutoExtension;
+struct S3JniAutoExtension {
+ jobject jObj /* Java object */;
+ jmethodID midFunc /* xEntryPoint() callback */;
+};
+
+/*
+** If true, modifying S3JniGlobal.metrics is protected by a mutex,
+** else it isn't.
+*/
+#ifdef SQLITE_DEBUG
+#define S3JNI_METRICS_MUTEX 1
+#else
+#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;
+ /* Cache of Java refs/IDs for NativePointerHolder subclasses.
+ ** Initialized on demand.
+ */
+ S3JniNphClass nph[NphCache_SIZE];
+ /*
+ ** Cache of per-thread state.
+ */
+ struct {
+ S3JniEnv * aHead /* Linked list of in-use instances */;
+ S3JniEnv * aFree /* Linked list of free instances */;
+ sqlite3_mutex * mutex /* mutex for aHead and aFree as well for
+ first-time inits of nph members. */;
+ void const * locker /* env mutex is held on this object's behalf.
+ Used only for sanity checking. */;
+ } envCache;
+ struct {
+ S3JniDb * aUsed /* Linked list of in-use instances */;
+ S3JniDb * aFree /* Linked list of free instances */;
+ sqlite3_mutex * mutex /* mutex for aUsed and aFree */;
+ void const * locker /* perDb mutex is held on this object's
+ behalf. Unlike envCache.locker, we cannot
+ always have this set to the current JNIEnv
+ object. Used only for sanity checking. */;
+ } perDb;
+#ifdef SQLITE_ENABLE_SQLLOG
+ struct {
+ S3JniHook sqllog /* sqlite3_config(SQLITE_CONFIG_SQLLOG) callback */;
+ } hooks;
+#endif
+ /*
+ ** Refs to global classes and methods. Obtained during static init
+ ** and never released.
+ */
struct {
jclass cObj /* global ref to java.lang.Object */;
jclass cLong /* global ref to java.lang.Long */;
@@ -374,170 +518,119 @@ struct S3JniEnvCache {
jmethodID stringGetBytes /* the String.getBytes(Charset) method */;
} g /* refs to global Java state */;
#ifdef SQLITE_ENABLE_FTS5
- jobject jFtsExt /* Global ref to Java singleton for the
- Fts5ExtensionApi instance. */;
struct {
- jclass klazz;
- jfieldID fidA;
- jfieldID fidB;
- } jPhraseIter;
+ 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
- S3JniEnvCache * pPrev /* Previous entry in the linked list */;
- S3JniEnvCache * pNext /* Next entry in the linked list */;
- /** TODO?: S3JniNphCache *pNphHit;
-
- and always set it to the most recent cache search result.
-
- The intent would be to help fast-track cache lookups and would
- speed up, e.g., the sqlite3_value-to-Java-array loop in a
- multi-threaded app.
- */
- S3JniNphCache nph[NphCache_SIZE];
-};
-
-static void S3JniNphCache_clear(JNIEnv * const env, S3JniNphCache * const p){
- UNREF_G(p->klazz);
- memset(p, 0, sizeof(S3JniNphCache));
-}
-
-#define S3JNI_ENABLE_AUTOEXT 1
-#if S3JNI_ENABLE_AUTOEXT
-/*
- Whether auto extensions are feasible here is currently unknown due
- to...
-
- 1) JNIEnv/threading issues. A db instance is mapped to a specific
- JNIEnv object but auto extensions may be added from any thread. In
- such contexts, which JNIEnv do we use for the JNI APIs?
-
- 2) a chicken/egg problem involving the Java/C mapping of the db:
- when auto extensions are run, the db has not yet been connected to
- Java. If we do that during the auto-ext, sqlite3_open(_v2)() will not behave
- properly because they have a different jobject and the API
- guarantees the user that _that_ object is the one the API will bind
- the native to.
-
- If we change the open(_v2()) interfaces to use OutputPointer.sqlite3
- instead of the client passing in an instance, we could work around
- (2).
-*/
-typedef struct S3JniAutoExtension S3JniAutoExtension;
-typedef void (*S3JniAutoExtension_xEntryPoint)(sqlite3*);
-struct S3JniAutoExtension {
- jobject jObj;
- jmethodID midFunc;
- S3JniAutoExtension_xEntryPoint xEntryPoint;
- S3JniAutoExtension *pNext /* next linked-list entry */;
- S3JniAutoExtension *pPrev /* previous linked-list entry */;
-};
-#endif
-
-/** State for various hook callbacks. */
-typedef struct S3JniHook S3JniHook;
-struct S3JniHook{
- jobject jObj /* global ref to Java instance */;
- jmethodID midCallback /* callback method. Signature depends on
- jObj's type */;
- jclass klazz /* global ref to jObj's class. Only needed
- by hooks which have an xDestroy() method,
- as lookup of that method is deferred
- until the object requires cleanup. */;
-};
-
-/**
- Per-(sqlite3*) state for various JNI bindings. This state is
- allocated as needed, cleaned up in sqlite3_close(_v2)(), and
- recycled when possible. It is freed during sqlite3_shutdown().
-*/
-typedef struct S3JniDb S3JniDb;
-struct S3JniDb {
- JNIEnv *env /* The associated JNIEnv handle */;
- sqlite3 *pDb /* The associated db handle */;
- jobject jDb /* A global ref of the object which was passed to
- sqlite3_open(_v2)(). We need this in order to have
- an object to pass to sqlite3_collation_needed()'s
- callback, or else we have to dynamically create one
- for that purpose, which would be fine except that
- it would be a different instance (and maybe even a
- different class) than the one the user may expect
- to receive. */;
- char * zMainDbName /* Holds any string allocated on behave of
- SQLITE_DBCONFIG_MAINDBNAME. */;
- S3JniHook busyHandler;
- S3JniHook collation;
- S3JniHook collationNeeded;
- S3JniHook commitHook;
- S3JniHook progress;
- S3JniHook rollbackHook;
- S3JniHook trace;
- S3JniHook updateHook;
- S3JniHook authHook;
-#ifdef SQLITE_ENABLE_FTS5
- jobject jFtsApi /* global ref to s3jni_fts5_api_from_db() */;
-#endif
- S3JniDb * pNext /* Next entry in the available/free list */;
- S3JniDb * pPrev /* Previous entry in the available/free list */;
-};
-
-/**
- Global state, e.g. caches and metrics.
-*/
-static struct {
- /**
- According to: https://developer.ibm.com/articles/j-jni/
-
- > A thread can get a JNIEnv by calling GetEnv() using the JNI
- invocation interface through a JavaVM object. The JavaVM object
- itself can be obtained by calling the JNI GetJavaVM() method
- using a JNIEnv object and can be cached and shared across
- threads. Caching a copy of the JavaVM object enables any thread
- with access to the cached object to get access to its own
- JNIEnv when necessary.
- */
- JavaVM * jvm;
+ /* Internal metrics. */
struct {
- S3JniEnvCache * aHead /* Linked list of in-use instances */;
- S3JniEnvCache * aFree /* Linked list of free instances */;
- } envCache;
- struct {
- S3JniDb * aUsed /* Linked list of in-use instances */;
- S3JniDb * aFree /* Linked list of free instances */;
- } perDb;
- struct {
- unsigned nphCacheHits;
- unsigned nphCacheMisses;
- unsigned envCacheHits;
- unsigned envCacheMisses;
- unsigned nDestroy /* xDestroy() calls across all types */;
+ volatile unsigned envCacheHits;
+ volatile unsigned envCacheMisses;
+ volatile unsigned envCacheAllocs;
+ volatile unsigned nMutexEnv /* number of times envCache.mutex was entered for
+ a S3JniEnv operation. */;
+ volatile unsigned nMutexEnv2 /* number of times envCache.mutex was entered for
+ a S3JniNphClass operation. */;
+ volatile unsigned nMutexPerDb /* number of times perDb.mutex was entered */;
+ volatile unsigned nMutexAutoExt /* number of times autoExt.mutex was entered */;
+ volatile unsigned nDestroy /* xDestroy() calls across all types */;
+ volatile unsigned nPdbAlloc /* Number of S3JniDb alloced. */;
+ volatile unsigned nPdbRecycled /* Number of S3JniDb 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;
- } 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;
+ unsigned nMetrics /* Total number of mutex-locked metrics increments. */;
+#if S3JNI_METRICS_MUTEX
+ sqlite3_mutex * mutex;
#endif
-} S3JniGlobal;
+ } metrics;
+ /**
+ The list of bound auto-extensions (Java-side:
+ org.sqlite.jni.AutoExtension objects).
+ */
+ struct {
+ S3JniAutoExtension *pExt /* Head of the auto-extension list */;
+ int nAlloc /* number of entries allocated for pExt,
+ as distinct from the number of active
+ entries. */;
+ int nExt /* number of active entries in pExt. */;
+ sqlite3_mutex * mutex /* mutex for manipulation/traversal of pExt */;
+ } autoExt;
+};
+static S3JniGlobalType S3JniGlobal = {};
+#define SJG S3JniGlobal
-#define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env)
-static void s3jni_oom(JNIEnv * const env){
+/* Increments *p, possibly protected by a mutex. */
+#if S3JNI_METRICS_MUTEX
+static void s3jni_incr( volatile unsigned int * const p ){
+ sqlite3_mutex * const m = SJG.metrics.mutex;
+ sqlite3_mutex_enter(m);
+ ++SJG.metrics.nMetrics;
+ ++(*p);
+ sqlite3_mutex_leave(m);
+}
+#else
+#define s3jni_incr(PTR) ++(*(PTR))
+#endif
+
+/* Helpers for working with specific mutexes. */
+#define MUTEX_ENV_ASSERT_LOCKED \
+ assert( 0 != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+#define MUTEX_ENV_ASSERT_LOCKER \
+ assert( (env) == SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+#define MUTEX_ENV_ASSERT_NOTLOCKER \
+ assert( (env) != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+#define MUTEX_ENV_ENTER \
+ MUTEX_ENV_ASSERT_NOTLOCKER; \
+ /*MARKER(("Entering ENV mutex@%p %s.\n", env));*/ \
+ sqlite3_mutex_enter( SJG.envCache.mutex ); \
+ ++SJG.metrics.nMutexEnv; \
+ SJG.envCache.locker = env
+#define MUTEX_ENV_LEAVE \
+ /*MARKER(("Leaving ENV mutex @%p %s.\n", env));*/ \
+ MUTEX_ENV_ASSERT_LOCKER; \
+ SJG.envCache.locker = 0; \
+ sqlite3_mutex_leave( SJG.envCache.mutex )
+#define MUTEX_EXT_ENTER \
+ /*MARKER(("Entering autoExt mutex@%p %s.\n", env));*/ \
+ sqlite3_mutex_enter( SJG.autoExt.mutex ); \
+ ++SJG.metrics.nMutexAutoExt
+#define MUTEX_EXT_LEAVE \
+ /*MARKER(("Leaving autoExt mutex@%p %s.\n", env));*/ \
+ sqlite3_mutex_leave( SJG.autoExt.mutex )
+#define MUTEX_NPH_ENTER \
+ MUTEX_ENV_ASSERT_NOTLOCKER; \
+ /*MARKER(("Entering NPH mutex@%p %s.\n", env));*/ \
+ sqlite3_mutex_enter( SJG.envCache.mutex ); \
+ ++SJG.metrics.nMutexEnv2; \
+ SJG.envCache.locker = env
+#define MUTEX_NPH_LEAVE \
+ /*MARKER(("Leaving NPH mutex @%p %s.\n", env));*/ \
+ MUTEX_ENV_ASSERT_LOCKER; \
+ SJG.envCache.locker = 0; \
+ sqlite3_mutex_leave( SJG.envCache.mutex )
+#define MUTEX_PDB_ENTER \
+ /*MARKER(("Entering PerDb mutex@%p %s.\n", env));*/ \
+ sqlite3_mutex_enter( SJG.perDb.mutex ); \
+ ++SJG.metrics.nMutexPerDb; \
+ SJG.perDb.locker = env;
+#define MUTEX_PDB_LEAVE \
+ /*MARKER(("Leaving PerDb mutex@%p %s.\n", env));*/ \
+ SJG.perDb.locker = 0; \
+ sqlite3_mutex_leave( SJG.perDb.mutex )
+
+#define s3jni_oom_check(VAR) if(!(VAR)) s3jni_oom(env)
+static inline void s3jni_oom(JNIEnv * const env){
(*env)->FatalError(env, "Out of memory.") /* does not return */;
}
@@ -559,68 +652,34 @@ static void * s3jni_malloc(JNIEnv * const env, size_t n){
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 * S3JniGlobal_env_cache(JNIEnv * const env){
+ struct S3JniEnv * row;
+ MUTEX_ENV_ENTER;
+ row = SJG.envCache.aHead;
for( ; row; row = row->pNext ){
if( row->env == env ){
- ++S3JniGlobal.metrics.envCacheHits;
+ s3jni_incr( &SJG.metrics.envCacheHits );
+ MUTEX_ENV_LEAVE;
return row;
}
}
- ++S3JniGlobal.metrics.envCacheMisses;
- row = S3JniGlobal.envCache.aFree;
+ s3jni_incr( &SJG.metrics.envCacheMisses );
+ 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(env, sizeof(S3JniEnv));
+ s3jni_incr( &SJG.metrics.envCacheAllocs );
}
memset(row, 0, sizeof(*row));
- row->pNext = S3JniGlobal.envCache.aHead;
+ row->pNext = SJG.envCache.aHead;
if(row->pNext) row->pNext->pPrev = row;
- S3JniGlobal.envCache.aHead = 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,
- "", "(J)V");
- EXCEPTION_IS_FATAL("Error getting reference to Long constructor.");
-
- row->g.cString = REF_G((*env)->FindClass(env,"java/lang/String"));
- EXCEPTION_IS_FATAL("Error getting reference to String class.");
- row->g.ctorStringBA =
- (*env)->GetMethodID(env, row->g.cString,
- "", "([BLjava/nio/charset/Charset;)V");
- EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor.");
- row->g.stringGetBytes =
- (*env)->GetMethodID(env, row->g.cString,
- "getBytes", "(Ljava/nio/charset/Charset;)[B");
- EXCEPTION_IS_FATAL("Error getting reference to String.getBytes(Charset).");
-
- { /* StandardCharsets.UTF_8 */
- jfieldID fUtf8;
- jclass const klazzSC =
- (*env)->FindClass(env,"java/nio/charset/StandardCharsets");
- EXCEPTION_IS_FATAL("Error getting reference to StndardCharsets class.");
- fUtf8 = (*env)->GetStaticFieldID(env, klazzSC, "UTF_8",
- "Ljava/nio/charset/Charset;");
- EXCEPTION_IS_FATAL("Error getting StndardCharsets.UTF_8 field.");
- row->g.oCharsetUtf8 =
- REF_G((*env)->GetStaticObjectField(env, klazzSC, fUtf8));
- EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8.");
- }
+ MUTEX_ENV_LEAVE;
return row;
}
@@ -638,23 +697,27 @@ 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).
+*/
+static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env,
+ const unsigned char * const p, int nP){
jbyteArray jba = (*env)->NewByteArray(env, (jint)nP);
if(jba){
(*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p);
@@ -662,22 +725,37 @@ static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char *
return jba;
}
-/**
- Uses the java.lang.String(byte[],Charset) constructor to create a
- new String from UTF-8 string z. n is the number of bytes to
- copy. If n<0 then sqlite3Strlen30() is used to calculate it.
-
- Returns NULL if z is NULL or on OOM, else returns a new jstring
- owned by the caller.
-
- Sidebar: this is a painfully inefficient way to convert from
- standard UTF-8 to a Java string, but JNI offers only algorithms for
- working with MUTF-8, not UTF-8.
+/*
+** Returns the current JNIEnv object. Fails fatally if it cannot find
+** it.
*/
-static jstring s3jni_utf8_to_jstring(S3JniEnvCache * const jc,
+static JNIEnv * s3jni_get_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;
+}
+/* Declares local var env = s3jni_get_env(). */
+#define LocalJniGetEnv JNIEnv * const env = s3jni_get_env()
+
+/*
+** Uses the java.lang.String(byte[],Charset) constructor to create a
+** new String from UTF-8 string z. n is the number of bytes to
+** copy. If n<0 then sqlite3Strlen30() is used to calculate it.
+**
+** Returns NULL if z is NULL or on OOM, else returns a new jstring
+** owned by the caller.
+**
+** Sidebar: this is a painfully inefficient way to convert from
+** standard UTF-8 to a Java string, but JNI offers only algorithms for
+** working with MUTF-8, not UTF-8.
+*/
+static jstring s3jni_utf8_to_jstring(JNIEnv * const env,
const char * const z, int n){
jstring rv = NULL;
- 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
@@ -688,39 +766,38 @@ static jstring s3jni_utf8_to_jstring(S3JniEnvCache * const jc,
if( n<0 ) n = sqlite3Strlen30(z);
jba = s3jni_new_jbyteArray(env, (unsigned const char *)z, (jsize)n);
if( jba ){
- rv = (*env)->NewObject(env, jc->g.cString, jc->g.ctorStringBA,
- jba, jc->g.oCharsetUtf8);
+ rv = (*env)->NewObject(env, SJG.g.cString, SJG.g.ctorStringBA,
+ jba, SJG.g.oCharsetUtf8);
UNREF_L(jba);
}
}
return rv;
}
-/**
- Converts the given java.lang.String object into a NUL-terminated
- UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8).
- Returns NULL if jstr is NULL or on allocation error. If jstr is not
- NULL and nLen is not NULL then nLen is set to the length of the
- returned string, not including the terminating NUL. If jstr is not
- NULL and it returns NULL, this indicates an allocation error. In
- that case, if nLen is not NULL then it is either set to 0 (if
- fetching of jstr's bytes fails to allocate) or set to what would
- have been the length of the string had C-string allocation
- succeeded.
-
- The returned memory is allocated from sqlite3_malloc() and
- ownership is transferred to the caller.
+/*
+** 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);
+ 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;
@@ -738,12 +815,12 @@ static char * s3jni_jstring_to_utf8(S3JniEnvCache * const jc,
return rv;
}
-/**
- Expects to be passed a pointer from sqlite3_column_text16() or
- sqlite3_value_text16() and a byte-length value from
- sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a
- Java String of exactly half that character length, returning NULL
- if !p or (*env)->NewString() fails.
+/*
+** 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
@@ -751,28 +828,26 @@ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p,
: NULL;
}
-/**
- Requires jx to be a Throwable. Calls its toString() method and
- returns its value converted to a UTF-8 string. The caller owns the
- returned string and must eventually sqlite3_free() it. Returns 0
- if there is a problem fetching the info or on OOM.
-
- Design note: we use toString() instead of getMessage() because the
- former includes the exception type's name:
-
- Exception e = new RuntimeException("Hi");
- System.out.println(e.toString()); // java.lang.RuntimeException: Hi
- System.out.println(e.getMessage()); // Hi
- }
+/*
+** Requires jx to be a Throwable. Calls its toString() method and
+** returns its value converted to a UTF-8 string. The caller owns the
+** returned string and must eventually sqlite3_free() it. Returns 0
+** if there is a problem fetching the info or on OOM.
+**
+** Design note: we use toString() instead of getMessage() because the
+** former includes the exception type's name:
+**
+** Exception e = new RuntimeException("Hi");
+** System.out.println(e.toString()); // java.lang.RuntimeException: Hi
+** System.out.println(e.getMessage()); // Hi
*/
-FIXME_THREADING(S3JniEnvCache)
static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){
- S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
jmethodID mid;
jstring msg;
char * zMsg;
jclass const klazz = (*env)->GetObjectClass(env, jx);
mid = (*env)->GetMethodID(env, klazz, "toString", "()Ljava/lang/String;");
+ UNREF_L(klazz);
IFTHREW{
EXCEPTION_REPORT;
EXCEPTION_CLEAR;
@@ -784,22 +859,22 @@ static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){
EXCEPTION_CLEAR;
return 0;
}
- zMsg = s3jni_jstring_to_utf8(jc, msg, 0);
+ zMsg = s3jni_jstring_to_utf8(env, msg, 0);
UNREF_L(msg);
return zMsg;
}
-/**
- Extracts the current JNI exception, sets ps->pDb's error message to
- its message string, and clears the exception. If errCode is non-0,
- it is used as-is, else SQLITE_ERROR is assumed. If there's a
- problem extracting the exception's message, it's treated as
- non-fatal and zDfltMsg is used in its place.
-
- This must only be called if a JNI exception is pending.
-
- Returns errCode unless it is 0, in which case SQLITE_ERROR is
- returned.
+/*
+** Extracts the current JNI exception, sets ps->pDb's error message to
+** its message string, and clears the exception. If errCode is non-0,
+** it is used as-is, else SQLITE_ERROR is assumed. If there's a
+** problem extracting the exception's message, it's treated as
+** non-fatal and zDfltMsg is used in its place.
+**
+** This must only be called if a JNI exception is pending.
+**
+** Returns errCode unless it is 0, in which case SQLITE_ERROR is
+** returned.
*/
static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps,
int errCode, const char *zDfltMsg){
@@ -817,195 +892,131 @@ static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps,
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 and 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){
+static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj){
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));
+ jclass const klazz = (*env)->GetObjectClass(env, jObj);
+ jmethodID method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V");
+
+ UNREF_L(klazz);
if(method){
- ++S3JniGlobal.metrics.nDestroy;
+ s3jni_incr( &SJG.metrics.nDestroy );
(*env)->CallVoidMethod(env, jObj, method);
IFTHREW{
EXCEPTION_WARN_CALLBACK_THREW("xDestroy() callback");
EXCEPTION_CLEAR;
}
}else{
+ /* Non-fatal. */
EXCEPTION_CLEAR;
}
}
}
-/**
- Removes any Java references from s and clears its state. If
- doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's
- s is passed to s3jni_call_xDestroy() before any references are
- cleared. It is legal to call this when the object has no Java
- references.
+/*
+** 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);
+ if(doXDestroy && s->jObj){
+ s3jni_call_xDestroy(env, s->jObj);
}
UNREF_G(s->jObj);
- UNREF_G(s->klazz);
memset(s, 0, sizeof(*s));
}
-/**
- Clears s's state and moves it to the free-list.
+/*
+** Clears s's state and moves it to the free-list.
*/
-FIXME_THREADING(perDb)
-static void S3JniDb_set_aside(S3JniDb * const s){
+static void S3JniDb_set_aside(JNIEnv * env, 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));
+ MUTEX_PDB_ENTER;
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.aUsed == s){
assert(!s->pPrev);
- S3JniGlobal.perDb.aUsed = s->pNext;
+ SJG.perDb.aUsed = s->pNext;
}
sqlite3_free( s->zMainDbName );
-#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->MEMBER, XDESTROY)
+#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->hooks.MEMBER, XDESTROY)
UNHOOK(trace, 0);
UNHOOK(progress, 0);
- UNHOOK(commitHook, 0);
- UNHOOK(rollbackHook, 0);
- UNHOOK(updateHook, 0);
- UNHOOK(authHook, 0);
+ UNHOOK(commit, 0);
+ UNHOOK(rollback, 0);
+ UNHOOK(update, 0);
+ UNHOOK(auth, 0);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ UNHOOK(preUpdate, 0);
+#endif
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;
+ 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;
+ MUTEX_PDB_LEAVE;
}
}
-/**
- Requires that p has been snipped from any linked list it is
- in. Clears all Java refs p holds and zeroes out p.
-*/
-static void S3JniEnvCache_clear(S3JniEnvCache * const p){
- JNIEnv * const env = p->env;
- if(env){
- int i;
- UNREF_G(p->g.cObj);
- UNREF_G(p->g.cLong);
- UNREF_G(p->g.cString);
- UNREF_G(p->g.oCharsetUtf8);
-#ifdef SQLITE_ENABLE_FTS5
- UNREF_G(p->jFtsExt);
- UNREF_G(p->jPhraseIter.klazz);
-#endif
- for( i = 0; i < NphCache_SIZE; ++i ){
- S3JniNphCache_clear(env, &p->nph[i]);
- }
- memset(p, 0, sizeof(S3JniEnvCache));
- }
-}
-
-/**
- Cleans up all state in S3JniGlobal.perDb for th given JNIEnv.
- Results are undefined if a Java-side db uses the API
- from the given JNIEnv after this call.
-*/
-FIXME_THREADING(perDb)
-static void S3JniDb_free_for_env(JNIEnv *env){
- S3JniDb * ps = S3JniGlobal.perDb.aUsed;
- S3JniDb * pNext = 0;
- for( ; ps; ps = pNext ){
- pNext = ps->pNext;
- if(ps->env == env){
- S3JniDb * const pPrev = ps->pPrev;
- S3JniDb_set_aside(ps);
- assert(pPrev ? pPrev->pNext!=ps : 1);
- pNext = pPrev;
- }
- }
-}
-
-/**
- Uncache any state for the given JNIEnv, clearing all Java
- references the cache owns. Returns true if env was cached and false
- if it was not found in the cache.
-
- Also passes env to S3JniDb_free_for_env() to free up
- what would otherwise be stale references.
+/*
+** 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.
*/
static int S3JniGlobal_env_uncache(JNIEnv * const env){
- struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead;
+ struct S3JniEnv * row;
+ MUTEX_ENV_ASSERT_LOCKED;
+ 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.
+**
+** It is up to the caller to populate the other members of the
+** returned object if needed, taking care to lock the population with
+** MUTEX_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_cache(JNIEnv * const env, S3NphRef const* pRef){
/**
- According to:
+ According to:
https://developer.ibm.com/articles/j-jni/
@@ -1019,128 +1030,79 @@ 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];
+ if( !pNC->pRef ){
+ MUTEX_NPH_ENTER;
+ if( !pNC->pRef ){
+ pNC->pRef = pRef;
+ pNC->klazz = (*env)->FindClass(env, pRef->zName);
+ EXCEPTION_IS_FATAL("FindClass() unexpectedly threw");
+ pNC->klazz = REF_G(pNC->klazz);
}
+ MUTEX_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;
+ return pNC;
}
-/**
- Returns the ID of the "nativePointer" field from the given
- NativePointerHolder class.
- */
-static jfieldID NativePointerHolder_getField(JNIEnv * const env, jclass klazz){
- jfieldID rv = (*env)->GetFieldID(env, klazz, "nativePointer", "J");
- EXCEPTION_IS_FATAL("Code maintenance required: missing nativePointer field.");
- return rv;
+/*
+** Returns the ID of the "nativePointer" field from the given
+** NativePointerHolder class.
+*/
+static jfieldID NativePointerHolder_getField(JNIEnv * const env, S3NphRef const* pRef){
+ S3JniNphClass * const pNC = S3JniGlobal_nph_cache(env, pRef);
+ if(!pNC->fidValue){
+ MUTEX_NPH_ENTER;
+ if(!pNC->fidValue){
+ pNC->fidValue = (*env)->GetFieldID(env, pNC->klazz, "nativePointer", "J");
+ EXCEPTION_IS_FATAL("Code maintenance required: missing nativePointer field.");
+ }
+ MUTEX_NPH_LEAVE;
+ }
+ return pNC->fidValue;
}
-/**
- 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.
+/*
+** Sets a native ptr value in NativePointerHolder object ppOut.
+** zClassName must be a static string so we can use its address
+** as a cache key.
*/
static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p,
- const char *zClassName){
- jfieldID setter = 0;
- S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName);
- if(pCache && pCache->klazz && pCache->fidValue){
- assert(zClassName == pCache->zClassName);
- setter = pCache->fidValue;
- assert(setter);
- }else{
- jclass const klazz =
- pCache ? pCache->klazz : (*env)->GetObjectClass(env, ppOut);
- setter = NativePointerHolder_getField(env, klazz);
- if(pCache){
- assert(pCache->klazz);
- assert(!pCache->fidValue);
- assert(zClassName == pCache->zClassName);
- pCache->fidValue = setter;
- }
- }
- (*env)->SetLongField(env, ppOut, setter, (jlong)p);
+ S3NphRef const* pRef){
+ (*env)->SetLongField(env, ppOut, NativePointerHolder_getField(env, pRef),
+ (jlong)p);
EXCEPTION_IS_FATAL("Could not set NativePointerHolder.nativePointer.");
}
-/**
- Fetches a native ptr value from NativePointerHolder object ppOut.
- zClassName must be a static string so we can use its address as a
- cache key.
+/*
+** 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. This is a no-op if pObj is NULL.
*/
-static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zClassName){
+static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, S3NphRef const* pRef){
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;
+ void * rv = (void*)(*env)->GetLongField(
+ env, pObj, NativePointerHolder_getField(env, pRef)
+ );
+ EXCEPTION_IS_FATAL("Cannot fetch NativePointerHolder.nativePointer.");
return rv;
}else{
return 0;
}
}
-/**
- Extracts the new S3JniDb instance from the free-list, or
- allocates one if needed, associats it with pDb, and returns.
- Returns NULL on OOM. pDb MUST be associated with jDb via
- NativePointerHolder_set().
+/*
+** 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){
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;
+ MUTEX_PDB_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);
@@ -1150,198 +1112,129 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb,
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){
+ 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 ){
+ rv->pNext = SJG.perDb.aUsed;
+ SJG.perDb.aUsed = rv;
if(rv->pNext){
assert(!rv->pNext->pPrev);
rv->pNext->pPrev = rv;
}
rv->jDb = REF_G(jDb);
rv->pDb = pDb;
- rv->env = env;
}
+ MUTEX_PDB_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
-
-/**
- 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 db.
+**
+** The 3rd argument should normally only be non-0 for routines which
+** are called from the C library and pass a native db handle instead of
+** a Java handle. In normal usage, the 2nd argument is a Java-side sqlite3
+** object, from which the db is fished out.
+**
+** Returns NULL if jDb and pDb are both NULL or if there is no
+** matching S3JniDb entry for pDb or the pointer fished out of jDb.
*/
-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;
+static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb){
+ S3JniDb * s = 0;
+ if(jDb || pDb){
+ MUTEX_PDB_ENTER;
+ s = SJG.perDb.aUsed;
+ if(!pDb){
+ assert( jDb );
+ pDb = PtrGet_sqlite3(jDb);
}
- }
- 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);
+ for( ; pDb && s; s = s->pNext){
+ if(s->pDb == pDb){
+ break;
+ }
+ }
+ MUTEX_PDB_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.
+/*
+** Unref any Java-side state in ax and zero out ax.
*/
-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;
+static void S3JniAutoExtension_clear(JNIEnv * const env,
+ S3JniAutoExtension * const ax){
+ if( ax->jObj ){
+ UNREF_G(ax->jObj);
+ memset(ax, 0, sizeof(*ax));
}
+}
+
+/*
+** Initializes a pre-allocated S3JniAutoExtension object. Returns
+** non-0 if there is an error collecting the required state from
+** jAutoExt (which must be an AutoExtension object). On error, it
+** passes ax to S3JniAutoExtension_clear().
+*/
+static int S3JniAutoExtension_init(JNIEnv *const env,
+ S3JniAutoExtension * const ax,
+ jobject const jAutoExt){
+ jclass const klazz = (*env)->GetObjectClass(env, jAutoExt);
+
+ ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint",
+ "(Lorg/sqlite/jni/sqlite3;)I");
+ UNREF_L(klazz);
+ EXCEPTION_WARN_IGNORE;
+ if(!ax->midFunc){
+ MARKER(("Error getting xEntryPoint(sqlite3) from AutoExtension object."));
+ S3JniAutoExtension_clear(env, ax);
+ return SQLITE_ERROR;
+ }
+ ax->jObj = REF_G(jAutoExt);
return 0;
}
-#endif
-#if S3JNI_ENABLE_AUTOEXT
-/**
- Unlink ax from S3JniGlobal.autoExt and free it.
-*/
-static void S3JniAutoExtension_free(JNIEnv * const env,
- S3JniAutoExtension * const ax){
- if( ax ){
- if( ax->pNext ) ax->pNext->pPrev = ax->pPrev;
- if( ax == S3JniGlobal.autoExt.pHead ){
- assert( !ax->pNext );
- S3JniGlobal.autoExt.pHead = ax->pNext;
- }else if( ax->pPrev ){
- ax->pPrev->pNext = ax->pNext;
- }
- ax->pNext = ax->pPrev = 0;
- UNREF_G(ax->jObj);
- sqlite3_free(ax);
- }
-}
-
-/**
- Allocates a new auto extension and plugs it in to S3JniGlobal.autoExt.
- Returns 0 on OOM or if there is an error collecting the required
- state from jAutoExt (which must be an AutoExtension object).
-*/
-static S3JniAutoExtension * S3JniAutoExtension_alloc(JNIEnv *const env,
- jobject const jAutoExt){
- S3JniAutoExtension * const ax = sqlite3_malloc(sizeof(*ax));
- if( ax ){
- jclass klazz;
- memset(ax, 0, sizeof(*ax));
- klazz = (*env)->GetObjectClass(env, jAutoExt);
- if(!klazz){
- S3JniAutoExtension_free(env, ax);
- return 0;
- }
- ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint",
- "(Lorg/sqlite/jni/sqlite3;)I");
- if(!ax->midFunc){
- MARKER(("Error getting xEntryPoint(sqlite3) from object."));
- S3JniAutoExtension_free(env, ax);
- return 0;
- }
- ax->jObj = REF_G(jAutoExt);
- ax->pNext = S3JniGlobal.autoExt.pHead;
- if( ax->pNext ) ax->pNext->pPrev = ax;
- S3JniGlobal.autoExt.pHead = ax;
- }
- return ax;
-}
-#endif /* S3JNI_ENABLE_AUTOEXT */
-
-/**
- Requires that jCx be a Java-side sqlite3_context wrapper for pCx.
- This function calls sqlite3_aggregate_context() to allocate a tiny
- sliver of memory, the address of which is set in
- jCx->aggregateContext. The memory is only used as a key for
- mapping client-side results of aggregate result sets across
- calls to the UDF's callbacks.
-
- isFinal must be 1 for xFinal() calls and 0 for all others, the
- difference being that the xFinal() invocation will not allocate
- new memory if it was not already, resulting in a value of 0
- for jCx->aggregateContext.
-
- Returns 0 on success. Returns SQLITE_NOMEM on allocation error,
- noting that it will not allocate when isFinal is true. It returns
- SQLITE_ERROR if there's a serious internal error in dealing with
- the JNI state.
+/*
+** Requires that jCx be a Java-side sqlite3_context wrapper for pCx.
+** This function calls sqlite3_aggregate_context() to allocate a tiny
+** sliver of memory, the address of which is set in
+** jCx->aggregateContext. The memory is only used as a key for
+** mapping client-side results of aggregate result sets across
+** calls to the UDF's callbacks.
+**
+** isFinal must be 1 for xFinal() calls and 0 for all others, the
+** difference being that the xFinal() invocation will not allocate
+** new memory if it was not already, resulting in a value of 0
+** for jCx->aggregateContext.
+**
+** Returns 0 on success. Returns SQLITE_NOMEM on allocation error,
+** noting that it will not allocate when isFinal is true. It returns
+** SQLITE_ERROR if there's a serious internal error in dealing with
+** the JNI state.
*/
static int udf_setAggregateContext(JNIEnv * env, jobject jCx,
sqlite3_context * pCx,
int isFinal){
- jfieldID member;
void * pAgg;
int rc = 0;
- S3JniNphCache * const pCache =
- S3JniGlobal_nph_cache(env, S3JniClassNames.sqlite3_context);
- if(pCache && pCache->klazz && pCache->fidSetAgg){
- member = pCache->fidSetAgg;
- assert(member);
- }else{
- jclass const klazz =
- pCache ? pCache->klazz : (*env)->GetObjectClass(env, jCx);
- member = (*env)->GetFieldID(env, klazz, "aggregateContext", "J");
- if( !member ){
- IFTHREW{ EXCEPTION_REPORT; EXCEPTION_CLEAR; }
- return s3jni_db_error(sqlite3_context_db_handle(pCx),
- SQLITE_ERROR,
- "Internal error: cannot find "
- "sqlite3_context::aggregateContext field.");
- }
- if(pCache){
- assert(pCache->klazz);
- assert(!pCache->fidSetAgg);
- pCache->fidSetAgg = member;
+ S3JniNphClass * const pNC =
+ S3JniGlobal_nph_cache(env, &S3NphRefs.sqlite3_context);
+ if(!pNC->fidAggCtx){
+ MUTEX_NPH_ENTER;
+ if(!pNC->fidAggCtx){
+ pNC->fidAggCtx = (*env)->GetFieldID(env, pNC->klazz, "aggregateContext", "J");
+ EXCEPTION_IS_FATAL("Cannot get sqlite3_contex.aggregateContext member.");
}
+ MUTEX_NPH_LEAVE;
}
- pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 4);
+ pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : sizeof(void*));
if( pAgg || isFinal ){
- (*env)->SetLongField(env, jCx, member, (jlong)pAgg);
+ (*env)->SetLongField(env, jCx, pNC->fidAggCtx, (jlong)pAgg);
}else{
assert(!pAgg);
rc = SQLITE_NOMEM;
@@ -1349,100 +1242,135 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx,
return rc;
}
-/**
- Common init for OutputPointer_set_Int32() and friends. zClassName must be a
- pointer from S3JniClassNames. jOut must be an instance of that
- class. Fetches the jfieldID for jOut's [value] property, which must
- be of the type represented by the JNI type signature zTypeSig, and
- stores it in pFieldId. Fails fatally if the property is not found,
- as that presents a serious internal misuse.
-
- Property lookups are cached on a per-zClassName basis. Do not use
- this routine with the same zClassName but different zTypeSig: it
- will misbehave.
+/*
+** Common init for OutputPointer_set_Int32() and friends. pRef must be
+** a pointer from S3NphRefs. jOut must be an instance of that
+** class. If necessary, this 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 pRef's S3JniGlobal.nph entry.
+** Fails fatally if the property is not found, as that presents a
+** serious internal misuse.
+**
+** Property lookups are cached on a per-pRef basis. Do not use this
+** routine with the same pRef but different zTypeSig: it will
+** misbehave.
*/
-static void setupOutputPointer(JNIEnv * const env, const char *zClassName,
- const char * const zTypeSig,
- jobject const jOut, jfieldID * const pFieldId){
- jfieldID setter = 0;
- S3JniNphCache * const pCache =
- S3JniGlobal_nph_cache(env, zClassName);
- if(pCache && pCache->klazz && pCache->fidValue){
- setter = pCache->fidValue;
- }else{
- const jclass klazz = (*env)->GetObjectClass(env, jOut);
- /*MARKER(("%s => %s\n", zClassName, zTypeSig));*/
- setter = (*env)->GetFieldID(env, klazz, "value", zTypeSig);
- EXCEPTION_IS_FATAL("setupOutputPointer() could not find OutputPointer.*.value");
- if(pCache){
- assert(!pCache->fidValue);
- pCache->fidValue = setter;
+static jfieldID setupOutputPointer(JNIEnv * const env, S3NphRef const * pRef,
+ const char * const zTypeSig,
+ jobject const jOut){
+ S3JniNphClass * const pNC = S3JniGlobal_nph_cache(env, pRef);
+ if(!pNC->fidValue){
+ MUTEX_NPH_ENTER;
+ if(!pNC->fidValue){
+ pNC->fidValue = (*env)->GetFieldID(env, pNC->klazz, "value", zTypeSig);
+ EXCEPTION_IS_FATAL("setupOutputPointer() could not find OutputPointer.*.value");
}
+ MUTEX_NPH_LEAVE;
}
- *pFieldId = setter;
+ return pNC->fidValue;
}
-/* Sets the value property of the OutputPointer.Int32 jOut object
- to v. */
+/*
+** 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);
+ jfieldID const setter = setupOutputPointer(
+ env, &S3NphRefs.OutputPointer_Int32, "I", jOut
+ );
(*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. */
+/*
+** 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);
+ jfieldID const setter = setupOutputPointer(
+ env, &S3NphRefs.OutputPointer_Int64, "J", jOut
+ );
(*env)->SetLongField(env, jOut, setter, v);
EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.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);
+ jobject jDb){
+ jfieldID const setter = setupOutputPointer(
+ env, &S3NphRefs.OutputPointer_sqlite3, "Lorg/sqlite/jni/sqlite3;", jOut
+ );
(*env)->SetObjectField(env, jOut, setter, jDb);
EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3.value");
}
+/*
+** 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);
+ jobject jStmt){
+ jfieldID const setter = setupOutputPointer(
+ env, &S3NphRefs.OutputPointer_sqlite3_stmt,
+ "Lorg/sqlite/jni/sqlite3_stmt;", jOut
+ );
(*env)->SetObjectField(env, jOut, setter, jStmt);
EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_stmt.value");
}
+#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){
+ jfieldID const setter = setupOutputPointer(
+ env, &S3NphRefs.OutputPointer_sqlite3_value,
+ "Lorg/sqlite/jni/sqlite3_value;", jOut
+ );
+ (*env)->SetObjectField(env, jOut, setter, jValue);
+ EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_value.value");
+}
+#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);
+ jfieldID const setter = setupOutputPointer(
+ env, &S3NphRefs.OutputPointer_ByteArray, "[B", jOut
+ );
(*env)->SetObjectField(env, jOut, setter, v);
EXCEPTION_IS_FATAL("Cannot set OutputPointer.ByteArray.value");
}
#endif
-/* Sets the value property of the OutputPointer.String jOut object
- to v. */
+
+/*
+** 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);
+ jfieldID const setter = setupOutputPointer(
+ env, &S3NphRefs.OutputPointer_String, "Ljava/lang/String;", jOut
+ );
(*env)->SetObjectField(env, jOut, setter, v);
EXCEPTION_IS_FATAL("Cannot set OutputPointer.String.value");
}
#endif /* SQLITE_ENABLE_FTS5 */
+/*
+** Returns true if eTextRep is a valid sqlite3 encoding constant, else
+** returns false.
+*/
static int encodingTypeIsValid(int eTextRep){
switch(eTextRep){
case SQLITE_UTF8: case SQLITE_UTF16:
@@ -1453,22 +1381,25 @@ static int encodingTypeIsValid(int eTextRep){
}
}
+/*
+** Proxy for Java-side Collation.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;
+ LocalJniGetEnv;
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){
+ UNREF_L(jbaLhs);
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,
+ rc = (*env)->CallIntMethod(env, ps->hooks.collation.jObj,
+ ps->hooks.collation.midCallback,
jbaLhs, jbaRhs);
EXCEPTION_IGNORE;
UNREF_L(jbaLhs);
@@ -1479,137 +1410,98 @@ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs,
/* 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 );
+ S3JniHook_unref( s3jni_get_env(), &ps->hooks.collation, 1 );
}
-/* State for sqlite3_result_java_object() and
- sqlite3_value_java_object(). */
+/*
+** State for sqlite3_result_java_object() and
+** sqlite3_value_java_object().
+**
+** TODO: this middle-man struct is no longer necessary. Conider
+** removing it and passing around jObj itself instead. OTOH, we might
+** find more state to pack in here.
+*/
typedef struct {
- /* The JNI docs say:
-
- https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html
-
- > The VM is guaranteed to pass the same interface pointer to a
- native method when it makes multiple calls to the native method
- from the same Java thread.
-
- Per the accompanying diagram, the "interface pointer" is the
- pointer-to-pointer which is passed to all JNI calls
- (`JNIEnv *env`), implying that we need to be caching that. The
- verbiage "interface pointer" implies, however, that we should be
- storing the dereferenced `(*env)` pointer.
-
- This posts claims it's unsafe to cache JNIEnv at all, even when
- it's always used in the same thread:
-
- https://stackoverflow.com/questions/12420463
-
- And this one seems to contradict that:
-
- https://stackoverflow.com/questions/13964608
-
- For later reference:
-
- https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/design.html#wp1242
-
- https://developer.android.com/training/articles/perf-jni
-
- The later has the following say about caching:
-
- > If performance is important, it's useful to look the
- [class/method ID] values up once and cache the results in your
- native code. Because there is a limit of one JavaVM per
- process, it's reasonable to store this data in a static local
- structure. ... The class references, field IDs, and method IDs
- are guaranteed valid until the class is unloaded. Classes are
- only unloaded if all classes associated with a ClassLoader can
- be garbage collected, which is rare but will not be impossible
- in Android. Note however that the jclass is a class reference
- and must be protected with a call to NewGlobalRef (see the next
- section).
- */
- JNIEnv * env;
jobject jObj;
} ResultJavaVal;
/* For use with sqlite3_result/value_pointer() */
-#define RESULT_JAVA_VAL_STRING "ResultJavaVal"
+#define ResultJavaValuePtrStr "org.sqlite.jni.ResultJavaVal"
+/*
+** Allocate a new ResultJavaVal and assign it a new global ref of
+** jObj. Caller owns the returned object and must eventually pass it
+** to ResultJavaVal_finalizer().
+*/
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;
}
+/*
+** If v is not NULL, it must point to a a ResultJavaVal object. Its
+** object reference is relinquished and v is freed.
+*/
static void ResultJavaVal_finalizer(void *v){
if(v){
ResultJavaVal * const rv = (ResultJavaVal*)v;
- if(rv->jObj) (*(rv->env))->DeleteGlobalRef(rv->env, rv->jObj);
+ LocalJniGetEnv;
+ UNREF_G(rv->jObj);
sqlite3_free(rv);
}
}
-/**
- Returns a new Java instance of the class named by zClassName, which
- MUST be interface-compatible with NativePointerHolder and MUST have
- a no-arg constructor. The NativePointerHolder_set() method is
- passed the new Java object and pNative. Hypothetically returns NULL
- if Java fails to allocate, but the JNI docs are not entirely clear
- on that detail.
-
- Always use a static string pointer from S3JniClassNames for the 2nd
- argument so that we can use its address as a cache key.
+/*
+** 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 S3NphRefs 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, S3NphRef 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, "", "()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_cache(env, pRef);
+ if(!pNC->midCtor){
+ MUTEX_NPH_ENTER;
+ if(!pNC->midCtor){
+ pNC->midCtor = (*env)->GetMethodID(env, pNC->klazz, "", "()V");
+ EXCEPTION_IS_FATAL("Cannot find constructor for class.");
}
+ MUTEX_NPH_LEAVE;
}
- assert(klazz);
- assert(ctor);
- rv = (*env)->NewObject(env, klazz, ctor);
+ rv = (*env)->NewObject(env, pNC->klazz, pNC->midCtor);
EXCEPTION_IS_FATAL("No-arg constructor threw.");
- if(rv) NativePointerHolder_set(env, rv, pNative, zClassName);
+ s3jni_oom_check(rv);
+ if(rv) NativePointerHolder_set(env, rv, pNative, pRef);
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, &S3NphRefs.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, &S3NphRefs.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, &S3NphRefs.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, &S3NphRefs.sqlite3_value, sv);
}
+/*
+** Type IDs for SQL function categories.
+*/
enum UDFType {
UDF_SCALAR = 1,
UDF_AGGREGATE,
@@ -1628,9 +1520,7 @@ typedef void (*udf_xFinal_f)(sqlite3_context*);
*/
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. */
@@ -1648,12 +1538,12 @@ static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){
"(Lorg/sqlite/jni/sqlite3_context;[Lorg/sqlite/jni/sqlite3_value;)V";
const char * zFV = /* signature for xFinal, xValue */
"(Lorg/sqlite/jni/sqlite3_context;)V";
+ jclass const klazz = (*env)->GetObjectClass(env, jObj);
+
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); \
+ s->Field = (*env)->GetMethodID(env, klazz, FuncName, FuncType); \
if(!s->Field) (*env)->ExceptionClear(env)
FGET("xFunc", zFSI, jmidxFunc);
FGET("xStep", zFSI, jmidxStep);
@@ -1661,6 +1551,7 @@ static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){
FGET("xValue", zFV, jmidxValue);
FGET("xInverse", zFSI, jmidxInverse);
#undef FGET
+ UNREF_L(klazz);
if(s->jmidxFunc) s->type = UDF_SCALAR;
else if(s->jmidxStep && s->jmidxFinal){
s->type = s->jmidxValue ? UDF_WINDOW : UDF_AGGREGATE;
@@ -1672,12 +1563,11 @@ static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){
}
static void S3JniUdf_free(S3JniUdf * s){
- JNIEnv * const env = s->env;
+ LocalJniGetEnv;
if(env){
//MARKER(("UDF cleanup: %s\n", s->zFuncName));
- s3jni_call_xDestroy(env, s->jObj, s->klazz);
+ s3jni_call_xDestroy(env, s->jObj);
UNREF_G(s->jObj);
- UNREF_G(s->klazz);
}
sqlite3_free(s->zFuncName);
sqlite3_free(s);
@@ -1701,10 +1591,6 @@ typedef struct {
Converts the given (cx, argc, argv) into arguments for the given
UDF, placing the result in the final argument. Returns 0 on
success, SQLITE_NOMEM on allocation error.
-
- TODO: see what we can do to optimize the
- new_sqlite3_value_wrapper() call. e.g. find the ctor a single time
- and call it here, rather than looking it up repeatedly.
*/
static int udf_args(JNIEnv *env,
sqlite3_context * const cx,
@@ -1717,7 +1603,7 @@ static int udf_args(JNIEnv *env,
*jArgv = 0;
if(!jcx) goto error_oom;
ja = (*env)->NewObjectArray(env, argc,
- S3JniGlobal_env_cache(env)->g.cObj,
+ SJG.g.cObj,
NULL);
if(!ja) goto error_oom;
for(i = 0; i < argc; ++i){
@@ -1736,47 +1622,73 @@ error_oom:
return SQLITE_NOMEM;
}
-static int udf_report_exception(sqlite3_context * cx,
- const char *zFuncName,
- const char *zFuncType){
- int rc;
- char * z =
- sqlite3_mprintf("Client-defined function %s.%s() threw. It should "
- "not do that.",
- zFuncName ? zFuncName : "", zFuncType);
- if(z){
- sqlite3_result_error(cx, z, -1);
- sqlite3_free(z);
- rc = SQLITE_ERROR;
+/*
+** 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 latter 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;
+
+ EXCEPTION_CLEAR;
+ zMsg = s3jni_exception_error_msg(env, ex);
+ z = sqlite3_mprintf("Client-defined SQL function %s.%s() threw: %s",
+ zFuncName ? zFuncName : "", 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;
+ MARKER(("Client-defined SQL function %s.%s() threw. "
+ "It should not do that.\n",
+ zFuncName ? zFuncName : "", zFuncType));
+ (*env)->ExceptionDescribe( env );
+ EXCEPTION_CLEAR;
}
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,
+static int udf_xFSI(sqlite3_context* const pCx, int argc,
+ sqlite3_value** const argv,
+ S3JniUdf * const s,
jmethodID xMethodID,
- const char * zFuncType){
- JNIEnv * const env = s->env;
+ const char * const zFuncType){
+ LocalJniGetEnv;
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;
+ int rc = udf_args(env, pCx, argc, argv, &args.jcx, &args.jargv);
+
//MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType));
+ if(rc) return rc;
if( UDF_SCALAR != s->type ){
rc = udf_setAggregateContext(env, args.jcx, pCx, 0);
}
if( 0 == rc ){
(*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv);
IFTHREW{
- rc = udf_report_exception(pCx, s->zFuncName, zFuncType);
+ rc = udf_report_exception(env, 'F'==zFuncType[1]/*xFunc*/, pCx,
+ s->zFuncName, zFuncType);
}
}
UNREF_L(args.jcx);
@@ -1784,69 +1696,76 @@ static int udf_xFSI(sqlite3_context* pCx, int argc,
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);
+ LocalJniGetEnv;
+ jobject jcx = new_sqlite3_context_wrapper(env, cx);
int rc = 0;
+ int const isFinal = 'F'==zFuncType[1]/*xFinal*/;
//MARKER(("%s.%s() cx = %p\n", s->zFuncName, zFuncType, cx));
if(!jcx){
- sqlite3_result_error_nomem(cx);
+ if( isFinal ) 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);
+ rc = udf_setAggregateContext(env, jcx, cx, isFinal);
}
if( 0 == rc ){
(*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx);
IFTHREW{
- rc = udf_report_exception(cx,s->zFuncName, zFuncType);
+ rc = udf_report_exception(env, isFinal, cx, s->zFuncName,
+ zFuncType);
}
}
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...
////////////////////////////////////////////////////////////////////////
WRAP_INT_STMT(1bind_1parameter_1count, sqlite3_bind_parameter_count)
WRAP_INT_DB(1changes, sqlite3_changes)
@@ -1866,6 +1785,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,187 +1804,224 @@ 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
+
/* 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_get_env();
+ jc = S3JniGlobal_env_cache(env);
+ ps = jc->pdbOpening;
+ if( !ps ){
+ MARKER(("Unexpected arrival of null S3JniDb in auto-extension runner.\n"));
+ *pzErr = sqlite3_mprintf("Unexpected arrival of null S3JniDb in auto-extension runner.");
return SQLITE_ERROR;
}
- assert( !ps->pDb /* it's still being opened */ );
+ jc->pdbOpening = 0;
+ assert( !ps->pDb && "it's still being opened" );
ps->pDb = pDb;
assert( ps->jDb );
- NativePointerHolder_set(env, ps->jDb, pDb, S3JniClassNames.sqlite3);
- ++S3JniGlobal.autoExt.isRunning;
- for( ; pAX; pAX = pAX->pNext ){
- rc = (*env)->CallIntMethod(env, pAX->jObj, pAX->midFunc, ps->jDb);
- IFTHREW {
- jthrowable const ex = (*env)->ExceptionOccurred(env);
- char * zMsg;
- EXCEPTION_CLEAR;
- zMsg = s3jni_exception_error_msg(env, ex);
- UNREF_L(ex);
- *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg);
- sqlite3_free(zMsg);
- rc = rc ? rc : SQLITE_ERROR;
- break;
- }else if( rc ){
- break;
+ NativePointerHolder_set(env, ps->jDb, pDb, &S3NphRefs.sqlite3);
+ 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. */;
+ MUTEX_EXT_ENTER;
+ if( i >= SJG.autoExt.nExt ){
+ go = 0;
+ }else{
+ ax.jObj = REF_L(SJG.autoExt.pExt[i].jObj);
+ ax.midFunc = SJG.autoExt.pExt[i].midFunc;
+ }
+ MUTEX_EXT_LEAVE;
+ if( ax.jObj ){
+ rc = (*env)->CallIntMethod(env, ax.jObj, ax.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);
+ if( !rc ) rc = SQLITE_ERROR;
+ }
+ UNREF_L(ax.jObj);
}
}
- --S3JniGlobal.autoExt.isRunning;
return rc;
}
-FIXME_THREADING(autoExt)
-JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){
+JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){
static int once = 0;
+ 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 );
- }
- 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. */;
+ MUTEX_EXT_ENTER;
+ for( i = 0; i < SJG.autoExt.nExt; ++i ){
+ /* Look for match or first empty slot. */
+ ax = &SJG.autoExt.pExt[i];
+ if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
+ MUTEX_EXT_LEAVE;
+ return 0 /* this as a no-op. */;
}
}
- return S3JniAutoExtension_alloc(env, jAutoExt) ? 0 : SQLITE_NOMEM;
+ if(i == SJG.autoExt.nExt ){
+ assert( SJG.autoExt.nExt <= SJG.autoExt.nAlloc );
+ if( SJG.autoExt.nExt == SJG.autoExt.nAlloc ){
+ unsigned n = 1 + SJG.autoExt.nAlloc;
+ S3JniAutoExtension * const aNew =
+ sqlite3_realloc( SJG.autoExt.pExt,
+ n * sizeof(S3JniAutoExtension) );
+ if( !aNew ){
+ rc = SQLITE_NOMEM;
+ }else{
+ SJG.autoExt.pExt = aNew;
+ ++SJG.autoExt.nAlloc;
+ }
+ }
+ if( 0==rc ){
+ ax = &SJG.autoExt.pExt[SJG.autoExt.nExt];
+ rc = S3JniAutoExtension_init(env, ax, jAutoExt);
+ assert( rc ? 0==ax->jObj : 0!=ax->jObj );
+ }
+ }
+ if( 0==rc ){
+ if( 0==once && ++once ){
+ rc = sqlite3_auto_extension( (void(*)(void))s3jni_run_java_auto_extensions );
+ if( rc ){
+ assert( ax );
+ S3JniAutoExtension_clear(env, ax);
+ }
+ }
+ if( 0==rc ){
+ ++SJG.autoExt.nExt;
+ }
+ }
+ MUTEX_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){
- int rc;
- if(!baData){
- rc = sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), ndx);
- }else{
- jbyte * const pBuf = JBA_TOC(baData);
- rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax,
- SQLITE_TRANSIENT);
- JBA_RELEASE(baData,pBuf);
- }
+ jbyte * const pBuf = baData ? s3jni_jbytearray_bytes(baData) : 0;
+ int const rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx,
+ pBuf, (int)nMax, SQLITE_TRANSIENT);
+ s3jni_jbytearray_release(baData,pBuf);
return (jint)rc;
}
-FIXME_THREADING(S3JniEnvCache)
JDECL(jint,1bind_1double)(JENV_CSELF, jobject jpStmt,
jint ndx, jdouble val){
return (jint)sqlite3_bind_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (double)val);
}
-FIXME_THREADING(S3JniEnvCache)
JDECL(jint,1bind_1int)(JENV_CSELF, jobject jpStmt,
jint ndx, jint val){
return (jint)sqlite3_bind_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val);
}
-FIXME_THREADING(S3JniEnvCache)
JDECL(jint,1bind_1int64)(JENV_CSELF, jobject jpStmt,
jint ndx, jlong val){
return (jint)sqlite3_bind_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val);
}
-FIXME_THREADING(S3JniEnvCache)
JDECL(jint,1bind_1null)(JENV_CSELF, jobject jpStmt,
jint ndx){
return (jint)sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
}
-FIXME_THREADING(S3JniEnvCache)
JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName){
int rc = 0;
- jbyte * const pBuf = JBA_TOC(jName);
+ 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);
- }
+ 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;
+}
+
+JDECL(jint,1bind_1text16)(JENV_CSELF, 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;
}
-FIXME_THREADING(S3JniEnvCache)
JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt,
jint ndx, jint n){
return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)n);
}
-FIXME_THREADING(S3JniEnvCache)
JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt,
jint ndx, jlong n){
return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n);
}
+/* 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);
+ if( ps->hooks.busyHandler.jObj ){
+ LocalJniGetEnv;
+ rc = (*env)->CallIntMethod(env, ps->hooks.busyHandler.jObj,
+ ps->hooks.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.");
+ EXCEPTION_WARN_CALLBACK_THREW("sqlite3_busy_handler() callback");
+ rc = s3jni_db_exception(env, ps, SQLITE_ERROR,
+ "sqlite3_busy_handler() callback threw.");
}
}
return rc;
}
-FIXME_THREADING(S3JniEnvCache)
JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){
- S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
+ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
int rc = 0;
if(!ps) return (jint)SQLITE_NOMEM;
if(jBusy){
- S3JniHook * const pHook = &ps->busyHandler;
+ S3JniHook * const pHook = &ps->hooks.busyHandler;
if(pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy)){
/* Same object - this is a no-op. */
return 0;
}
+ jclass klazz;
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");
+ klazz = (*env)->GetObjectClass(env, jBusy);
+ pHook->midCallback = (*env)->GetMethodID(env, klazz, "xCallback", "(I)I");
+ UNREF_L(klazz);
IFTHREW {
S3JniHook_unref(env, pHook, 0);
rc = SQLITE_ERROR;
@@ -2069,73 +2030,75 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){
return rc;
}
}else{
- S3JniHook_unref(env, &ps->busyHandler, 1);
+ S3JniHook_unref(env, &ps->hooks.busyHandler, 1);
}
return jBusy
? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps)
: sqlite3_busy_handler(ps->pDb, 0, 0);
}
-FIXME_THREADING(S3JniEnvCache)
-FIXME_THREADING(perDb)
JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){
- S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
+ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
if( ps ){
- S3JniHook_unref(env, &ps->busyHandler, 1);
+ S3JniHook_unref(env, &ps->hooks.busyHandler, 1);
return sqlite3_busy_timeout(ps->pDb, (int)ms);
}
return SQLITE_MISUSE;
}
-#if S3JNI_ENABLE_AUTOEXT
-FIXME_THREADING(autoExt)
JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){
- S3JniAutoExtension * ax;;
- if( S3JniGlobal.autoExt.isRunning ) return JNI_FALSE;
- for( ax = S3JniGlobal.autoExt.pHead; ax; ax = ax->pNext ){
- if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
- S3JniAutoExtension_free(env, ax);
- return JNI_TRUE;
+ S3JniAutoExtension * ax;
+ jboolean rc = JNI_FALSE;
+ int i;
+ MUTEX_EXT_ENTER;
+ /* This algo mirrors the one in the core. */
+ for( i = SJG.autoExt.nExt-1; i >= 0; --i ){
+ ax = &SJG.autoExt.pExt[i];
+ if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
+ S3JniAutoExtension_clear(env, ax);
+ /* Move final entry into this slot. */
+ --SJG.autoExt.nExt;
+ *ax = SJG.autoExt.pExt[SJG.autoExt.nExt];
+ memset(&SJG.autoExt.pExt[SJG.autoExt.nExt], 0,
+ sizeof(S3JniAutoExtension));
+ assert(! SJG.autoExt.pExt[SJG.autoExt.nExt].jObj );
+ rc = JNI_TRUE;
+ break;
}
}
- return JNI_FALSE;
+ MUTEX_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;
assert(version == 1 || version == 2);
- ps = S3JniDb_for_db(env, jDb, 0, 0);
+ ps = S3JniDb_for_db(env, jDb, 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( 0==rc ){
+ S3JniDb_set_aside(env, ps)
+ /* MUST come after close() because of ps->trace. */;
+ NativePointerHolder_set(env, jDb, 0, &S3NphRefs.sqlite3);
+ }
}
return (jint)rc;
}
-FIXME_THREADING(S3JniEnvCache)
-FIXME_THREADING(perDb)
JDECL(jint,1close_1v2)(JENV_CSELF, jobject pDb){
return s3jni_close_db(env, pDb, 2);
}
-FIXME_THREADING(S3JniEnvCache)
-FIXME_THREADING(perDb)
JDECL(jint,1close)(JENV_CSELF, jobject pDb){
return s3jni_close_db(env, pDb, 1);
}
-/**
- Assumes z is an array of unsigned short and returns the index in
- that array of the first element with the value 0.
+/*
+** 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,37 +2107,33 @@ 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;
+ LocalJniGetEnv;
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,
+ (*env)->CallVoidMethod(env, ps->hooks.collationNeeded.jObj,
+ ps->hooks.collationNeeded.midCallback,
ps->jDb, (jint)eTextRep, jName);
IFTHREW{
- s3jni_db_exception(env, ps, 0, "collation-needed callback threw");
+ s3jni_db_exception(env, ps, 0, "sqlite3_collation_needed() callback threw");
}
+ UNREF_L(jName);
}
- 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);
+ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
jclass klazz;
jobject pOld = 0;
jmethodID xCallback;
- S3JniHook * const pHook = &ps->collationNeeded;
+ S3JniHook * const pHook = &ps->hooks.collationNeeded;
int rc;
if( !ps ) return SQLITE_MISUSE;
@@ -2192,6 +2151,7 @@ JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){
klazz = (*env)->GetObjectClass(env, jHook);
xCallback = (*env)->GetMethodID(env, klazz, "xCollationNeeded",
"(Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I");
+ UNREF_L(klazz);
IFTHREW {
rc = s3jni_db_exception(env, ps, SQLITE_MISUSE,
"Cannot not find matching callback on "
@@ -2205,7 +2165,6 @@ JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){
return rc;
}
-FIXME_THREADING(S3JniEnvCache)
JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt,
jint ndx){
sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
@@ -2219,34 +2178,29 @@ JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt,
}
}
-FIXME_THREADING(S3JniEnvCache)
JDECL(jdouble,1column_1double)(JENV_CSELF, jobject jpStmt,
jint ndx){
return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
}
-FIXME_THREADING(S3JniEnvCache)
JDECL(jint,1column_1int)(JENV_CSELF, jobject jpStmt,
jint ndx){
return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
}
-FIXME_THREADING(S3JniEnvCache)
JDECL(jlong,1column_1int64)(JENV_CSELF, jobject jpStmt,
jint ndx){
return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
}
-FIXME_THREADING(S3JniEnvCache)
-JDECL(jbyteArray,1column_1text)(JENV_CSELF, jobject jpStmt,
+JDECL(jbyteArray,1column_1text_1utf8)(JENV_CSELF, jobject jpStmt,
jint ndx){
sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
const int n = sqlite3_column_bytes(stmt, (int)ndx);
const unsigned char * const p = sqlite3_column_text(stmt, (int)ndx);
- return s3jni_new_jbyteArray(env, p, n);
+ return p ? s3jni_new_jbyteArray(env, p, n) : NULL;
}
-FIXME_THREADING(S3JniEnvCache)
JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt,
jint ndx){
sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
@@ -2255,7 +2209,6 @@ JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt,
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);
@@ -2264,12 +2217,12 @@ JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt,
static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){
- JNIEnv * const env = ps->env;
+ LocalJniGetEnv;
int rc = isCommit
- ? (int)(*env)->CallIntMethod(env, ps->commitHook.jObj,
- ps->commitHook.midCallback)
- : (int)((*env)->CallVoidMethod(env, ps->rollbackHook.jObj,
- ps->rollbackHook.midCallback), 0);
+ ? (int)(*env)->CallIntMethod(env, ps->hooks.commit.jObj,
+ ps->hooks.commit.midCallback)
+ : (int)((*env)->CallVoidMethod(env, ps->hooks.rollback.jObj,
+ ps->hooks.rollback.midCallback), 0);
IFTHREW{
EXCEPTION_CLEAR;
rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "hook callback threw.");
@@ -2285,14 +2238,14 @@ 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_for_db(env, jDb, 0);
jclass klazz;
jobject pOld = 0;
jmethodID xCallback;
- S3JniHook * const pHook = isCommit ? &ps->commitHook : &ps->rollbackHook;
+ S3JniHook * const pHook =
+ isCommit ? &ps->hooks.commit : &ps->hooks.rollback;
if(!ps){
s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
return 0;
@@ -2317,6 +2270,7 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobje
xCallback = (*env)->GetMethodID(env, klazz,
isCommit ? "xCommitHook" : "xRollbackHook",
isCommit ? "()I" : "()V");
+ UNREF_L(klazz);
IFTHREW {
EXCEPTION_REPORT;
EXCEPTION_CLEAR;
@@ -2343,66 +2297,158 @@ JDECL(jobject,1commit_1hook)(JENV_CSELF,jobject jDb, jobject jHook){
JDECL(jstring,1compileoption_1get)(JENV_CSELF, jint n){
- return (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) );
+ return (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) )
+ /* We know these to be ASCII, so MUTF-8 is fine. */;
}
JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){
- const char *zUtf8 = JSTR_TOC(name);
+ const char *zUtf8 = s3jni_jstring_to_mutf8(name);
const jboolean rc =
0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE;
- JSTR_RELEASE(name, zUtf8);
+ s3jni_mutf8_release(name, zUtf8);
return rc;
}
-FIXME_THREADING(perDb)
+/*
+** sqlite3_config(SQLITE_CONFIG_...) wrapper for a small subset of
+** options.
+*/
+JDECL(jint,1config__I)(JENV_CSELF, 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;
+ LocalJniGetEnv;
+ S3JniDb * const ps = S3JniDb_for_db(env, 0, pDb);
+ S3JniHook * const hook = &SJG.hooks.sqllog;
+
+ if( !ps || !hook->jObj ) return;
+ jArg0 = REF_L(ps->jDb);
+ switch(op){
+ case 0: /* db opened */
+ case 1: /* SQL executed */
+ jArg1 = s3jni_utf8_to_jstring(env, 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);
+ IFTHREW{
+ EXCEPTION_WARN_CALLBACK_THREW("SQLITE_CONFIG_SQLLOG callback");
+ EXCEPTION_CLEAR;
+ }
+ UNREF_L(jArg0);
+ UNREF_L(jArg1);
+}
+//! Requirement of SQLITE_CONFIG_SQLLOG.
+void sqlite3_init_sqllog(void){
+ sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 );
+}
+#endif
+
+/* sqlite3_config(SQLITE_CONFIG_SQLLOG) wrapper. */
+JDECL(jint,1config__Lorg_sqlite_jni_SQLLog_2)(JENV_CSELF, jobject jLog){
+#ifdef SQLITE_ENABLE_SQLLOG
+ S3JniHook tmpHook;
+ S3JniHook * const hook = &tmpHook;
+ S3JniHook * const hookOld = & SJG.hooks.sqllog;
+ jclass klazz;
+ int rc = 0;
+ if( !jLog ){
+ S3JniHook_unref(env, hookOld, 0);
+ return 0;
+ }
+ if( hookOld->jObj && (*env)->IsSameObject(env, jLog, hookOld->jObj) ){
+ return 0;
+ }
+ klazz = (*env)->GetObjectClass(env, jLog);
+ hook->midCallback = (*env)->GetMethodID(env, klazz, "xSqllog",
+ "(Lorg/sqlite/jni/sqlite3;"
+ "Ljava/lang/String;"
+ "I)V");
+ UNREF_L(klazz);
+ if( !hook->midCallback ){
+ EXCEPTION_WARN_IGNORE;
+ S3JniHook_unref(env, hook, 0);
+ return SQLITE_ERROR;
+ }
+ hook->jObj = REF_G(jLog);
+ rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 );
+ if( rc ){
+ S3JniHook_unref(env, hook, 0);
+ }else{
+ S3JniHook_unref(env, hookOld, 0);
+ *hookOld = *hook;
+ }
+ return rc;
+#else
+ MARKER(("Warning: built without SQLITE_ENABLE_SQLLOG.\n"));
+ return SQLITE_MISUSE;
+#endif
+}
+
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;
+ S3JniDb * const ps = pDb ? S3JniDb_for_db(env, 0, 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;
int rc;
const char *zName;
- S3JniHook * pHook;
- if(!ps) return (jint)SQLITE_NOMEM;
- pHook = &ps->collation;
+ jclass klazz;
+ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
+ S3JniHook * const pHook = ps ? &ps->hooks.collation : 0;
+
+ if( !pHook ) return SQLITE_MISUSE;
klazz = (*env)->GetObjectClass(env, oCollation);
pHook->midCallback = (*env)->GetMethodID(env, klazz, "xCompare",
"([B[B)I");
+ UNREF_L(klazz);
IFTHREW{
- EXCEPTION_REPORT;
+ UNREF_L(klazz);
return s3jni_db_error(ps->pDb, SQLITE_ERROR,
"Could not get xCompare() method for object.");
}
- zName = JSTR_TOC(name);
+ zName = s3jni_jstring_to_mutf8(name);
rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep,
ps, CollationState_xCompare,
CollationState_xDestroy);
- JSTR_RELEASE(name, zName);
+ s3jni_mutf8_release(name, zName);
if( 0==rc ){
pHook->jObj = REF_G(oCollation);
- pHook->klazz = REF_G(klazz);
}else{
S3JniHook_unref(env, pHook, 1);
}
return (jint)rc;
}
-static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName,
- jint nArg, jint eTextRep, jobject jFunctor){
+JDECL(jint,1create_1function)(JENV_CSELF, 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;
@@ -2412,7 +2458,7 @@ static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName,
S3JniUdf_free(s);
goto error_cleanup;
}
- zFuncName = JSTR_TOC(jFuncName);
+ zFuncName = s3jni_jstring_to_utf8(env,jFuncName,0);
if(!zFuncName){
rc = SQLITE_NOMEM;
S3JniUdf_free(s);
@@ -2436,34 +2482,24 @@ 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() */
+ sqlite3_free(zFuncName);
+ /* on sqlite3_create_function() error, s will be destroyed via
+ ** create_function(), so we're not leaking s. */
return (jint)rc;
}
-JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName,
- jint nArg, jint eTextRep, jobject jFunctor){
- return create_function(env, jDb, jFuncName, nArg, eTextRep, jFunctor);
-}
-
/* sqlite3_db_config() for (int,const char *) */
JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)(
JENV_CSELF, jobject jDb, jint op, jstring jStr
){
- S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
+ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
int rc;
char *zStr;
switch( (ps && jStr) ? op : 0 ){
case SQLITE_DBCONFIG_MAINDBNAME:
- zStr = s3jni_jstring_to_utf8(S3JniGlobal_env_cache(env), jStr, 0);
+ zStr = s3jni_jstring_to_utf8(env, jStr, 0);
if( zStr ){
rc = sqlite3_db_config(ps->pDb, (int)op, zStr);
if( rc ){
@@ -2482,14 +2518,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. */
+/*
+** ACHTUNG: 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.
+*/
JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)(
JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut
){
- S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
+ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
int rc;
switch( ps ? op : 0 ){
case SQLITE_DBCONFIG_ENABLE_FKEY:
@@ -2523,11 +2561,11 @@ 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
@@ -2538,8 +2576,7 @@ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer
}
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);
+ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
char *zDbName;
jstring jRv = 0;
int nStr = 0;
@@ -2547,12 +2584,12 @@ JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){
if( !ps || !jDbName ){
return 0;
}
- zDbName = s3jni_jstring_to_utf8(jc, jDbName, &nStr);
+ zDbName = s3jni_jstring_to_utf8(env, 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);
+ jRv = s3jni_utf8_to_jstring(env, zRv, -1);
}
}
return jRv;
@@ -2579,25 +2616,23 @@ JDECL(jint,1errcode)(JENV_CSELF, jobject jpDb){
JDECL(jstring,1errmsg)(JENV_CSELF, jobject jpDb){
sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
- S3JniEnvCache * const jc = pDb ? S3JniGlobal_env_cache(env) : 0;
- return jc ? s3jni_utf8_to_jstring(jc, sqlite3_errmsg(pDb), -1) : 0;
+ return pDb ? s3jni_utf8_to_jstring(env, 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 */;
+ /* We know these values to be plain ASCII, so pose no MUTF-8
+ ** incompatibility */;
}
JDECL(jstring,1expanded_1sql)(JENV_CSELF, jobject jpStmt){
sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
jstring rv = 0;
if( pStmt ){
- S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
char * zSql = sqlite3_expanded_sql(pStmt);
- OOM_CHECK(zSql);
+ s3jni_oom_check(zSql);
if( zSql ){
- rv = s3jni_utf8_to_jstring(jc, zSql, -1);
+ rv = s3jni_utf8_to_jstring(env, zSql, -1);
sqlite3_free(zSql);
}
}
@@ -2619,70 +2654,89 @@ JDECL(jint,1finalize)(JENV_CSELF, jobject jpStmt){
sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
if( pStmt ){
rc = sqlite3_finalize(pStmt);
- NativePointerHolder_set(env, jpStmt, 0, S3JniClassNames.sqlite3_stmt);
+ NativePointerHolder_set(env, jpStmt, 0, &S3NphRefs.sqlite3_stmt);
}
return rc;
}
+JDECL(void,1interrupt)(JENV_CSELF, jobject jpDb){
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ if( pDb ) sqlite3_interrupt(pDb);
+}
+
+JDECL(jboolean,1is_1interrupted)(JENV_CSELF, jobject jpDb){
+ int rc = 0;
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ if( pDb ){
+ rc = sqlite3_is_interrupted(pDb);
+ }
+ return rc ? JNI_TRUE : JNI_FALSE;
+}
+
JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){
return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb));
}
-//! Pre-open() code common to sqlite3_open(_v2)().
-static int s3jni_open_pre(JNIEnv * const env, S3JniEnvCache **jc,
+/* 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){
+ S3JniDb ** ps){
+ int rc = 0;
+ jobject jDb = 0;
*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 ){
+ if(!*jc){
+ rc = SQLITE_NOMEM;
+ goto end;
+ }
+ *zDbName = jDbName ? s3jni_jstring_to_utf8(env, 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
+ *ps = S3JniDb_alloc(env, 0, jDb);
if(*ps){
- assert(!S3JniGlobal.autoExt.psOpening);
- S3JniGlobal.autoExt.psOpening = *ps;
+ (*jc)->pdbOpening = *ps;
+ }else{
+ UNREF_L(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).
+**
+** 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
+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(env, ps->jDb, *ppDb, &S3NphRefs.sqlite3);
+ }else{
+ assert( ps->pDb == *ppDb /* set up via s3jni_run_java_auto_extensions() */);
+ }
}else{
- S3JniDb_set_aside(ps);
+ S3JniDb_set_aside(env, ps);
ps = 0;
}
OutputPointer_set_sqlite3(env, jOut, ps ? ps->jDb : 0);
@@ -2692,20 +2746,16 @@ static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps,
JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){
sqlite3 * pOut = 0;
char *zName = 0;
- jobject jDb = 0;
S3JniDb * ps = 0;
- S3JniEnvCache * jc = 0;
- S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening;
- int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb);
+ 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, rc);
assert(rc==0 ? pOut!=0 : 1);
sqlite3_free(zName);
}
- S3JniGlobal.autoExt.psOpening = prevOpening;
return (jint)rc;
}
@@ -2713,14 +2763,12 @@ JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName,
jobject jOut, jint flags, jstring strVfs){
sqlite3 * pOut = 0;
char *zName = 0;
- jobject jDb = 0;
S3JniDb * ps = 0;
- S3JniEnvCache * jc = 0;
+ S3JniEnv * jc = 0;
char *zVfs = 0;
- S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening;
- int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb);
+ int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps);
if( 0==rc && strVfs ){
- zVfs = s3jni_jstring_to_utf8(jc, strVfs, 0);
+ zVfs = s3jni_jstring_to_utf8(env, strVfs, 0);
if( !zVfs ){
rc = SQLITE_NOMEM;
}
@@ -2728,14 +2776,10 @@ JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName,
if( 0==rc ){
rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs);
}
- //MARKER(("open_v2() ps@%p db@%p\n", ps, pOut));
- /*MARKER(("zName=%s, zVfs=%s, pOut=%p, flags=%d, nrc=%d\n",
- zName, zVfs, pOut, (int)flags, nrc));*/
- rc = s3jni_open_post(env, ps, &pOut, jOut, rc);
+ rc = s3jni_open_post(env, jc, ps, &pOut, jOut, rc);
assert(rc==0 ? pOut!=0 : 1);
sqlite3_free(zName);
sqlite3_free(zVfs);
- S3JniGlobal.autoExt.psOpening = prevOpening;
return (jint)rc;
}
@@ -2747,7 +2791,7 @@ static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass
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 ){
@@ -2774,18 +2818,19 @@ 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(env, jStmt, pStmt, &S3NphRefs.sqlite3_stmt);
}else{
- /* Happens for comments and whitespace */
+ /* Happens for comments and whitespace. */
UNREF_L(jStmt);
jStmt = 0;
}
@@ -2817,12 +2862,201 @@ JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jDb, jbyteArra
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;
+ LocalJniGetEnv;
+ jstring jDbName;
+ jstring jTable;
+ S3JniHook * pHook;
+ const int isPre = 0!=pDb;
+ pHook = isPre ?
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ &ps->hooks.preUpdate
+#else
+ 0
+#endif
+ : &ps->hooks.update;
+
+ assert( pHook );
+ jDbName = s3jni_utf8_to_jstring(env, zDb, -1);
+ jTable = jDbName ? s3jni_utf8_to_jstring(env, zTable, -1) : 0;
+ IFTHREW {
+ EXCEPTION_CLEAR;
+ s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
+ }else{
+ assert( pHook->jObj );
+ assert( pHook->midCallback );
+ assert( ps->jDb );
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( isPre ) (*env)->CallVoidMethod(env, pHook->jObj, pHook->midCallback,
+ ps->jDb, (jint)opId, jDbName, jTable,
+ (jlong)iKey1, (jlong)iKey2);
+ else
+#endif
+ (*env)->CallVoidMethod(env, pHook->jObj, pHook->midCallback,
+ (jint)opId, jDbName, jTable, (jlong)iKey1);
+ IFTHREW{
+ EXCEPTION_WARN_CALLBACK_THREW("sqlite3_(pre)update_hook() callback");
+ s3jni_db_exception(env, ps, 0,
+ "sqlite3_(pre)update_hook() callback threw");
+ }
+ }
+ UNREF_L(jDbName);
+ UNREF_L(jTable);
+}
+
+#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}() */
+JDECL(int,1preupdate_1blobwrite)(JENV_CSELF, jobject jDb){ return SQLITE_MISUSE; }
+JDECL(int,1preupdate_1count)(JENV_CSELF, jobject jDb){ return SQLITE_MISUSE; }
+JDECL(int,1preupdate_1depth)(JENV_CSELF, 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_for_db(env, jDb, 0);
+ jclass klazz;
+ jobject pOld = 0;
+ jmethodID xCallback;
+ S3JniHook * pHook = ps ? (
+ isPre ?
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ &ps->hooks.preUpdate
+#else
+ 0
+#endif
+ : &ps->hooks.update) : 0;
+
+ if(!pHook){
+ 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));
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( isPre ) sqlite3_preupdate_hook(ps->pDb, 0, 0);
+ else
+#endif
+ sqlite3_update_hook(ps->pDb, 0, 0);
+ return pOld;
+ }
+ klazz = (*env)->GetObjectClass(env, jHook);
+ xCallback = isPre
+ ? (*env)->GetMethodID(env, klazz, "xPreUpdate",
+ "(Lorg/sqlite/jni/sqlite3;"
+ "I"
+ "Ljava/lang/String;"
+ "Ljava/lang/String;"
+ "JJ)V")
+ : (*env)->GetMethodID(env, klazz, "xUpdateHook",
+ "(ILjava/lang/String;Ljava/lang/String;J)V");
+ UNREF_L(klazz);
+ IFTHREW {
+ EXCEPTION_CLEAR;
+ s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Cannot not find matching callback on "
+ "(pre)update hook object.");
+ }else{
+ pHook->midCallback = xCallback;
+ pHook->jObj = REF_G(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 = REF_L(pOld);
+ UNREF_G(pOld);
+ pOld = tmp;
+ }
+ }
+ return pOld;
+}
+
+
+JDECL(jobject,1preupdate_1hook)(JENV_CSELF, 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);
+ UNREF_L(pWrap);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ }
+ return rc;
+#else
+ return SQLITE_MISUSE;
+#endif
+}
+JDECL(jint,1preupdate_1new)(JENV_CSELF, jobject jDb, jint iCol, jobject jOut){
+ return s3jni_preupdate_newold(env, 1, jDb, iCol, jOut);
+}
+JDECL(jint,1preupdate_1old)(JENV_CSELF, 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);
+ LocalJniGetEnv;
+ int rc = (int)(*env)->CallIntMethod(env, ps->hooks.progress.jObj,
+ ps->hooks.progress.midCallback);
IFTHREW{
rc = s3jni_db_exception(env, ps, rc,
"sqlite3_progress_handler() callback threw");
@@ -2831,13 +3065,13 @@ static int s3jni_progress_handler_impl(void *pP){
}
JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress){
- S3JniDb * ps = S3JniDb_for_db(env, jDb, 0, 0);
+ S3JniDb * ps = S3JniDb_for_db(env, jDb, 0);
jclass klazz;
jmethodID xCallback;
if( n<1 || !jProgress ){
if(ps){
- UNREF_G(ps->progress.jObj);
- memset(&ps->progress, 0, sizeof(ps->progress));
+ UNREF_G(ps->hooks.progress.jObj);
+ memset(&ps->hooks.progress, 0, sizeof(ps->hooks.progress));
}
sqlite3_progress_handler(ps->pDb, 0, 0, 0);
return;
@@ -2848,15 +3082,16 @@ JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress
}
klazz = (*env)->GetObjectClass(env, jProgress);
xCallback = (*env)->GetMethodID(env, klazz, "xCallback", "()I");
+ UNREF_L(klazz);
IFTHREW {
EXCEPTION_CLEAR;
s3jni_db_error(ps->pDb, SQLITE_ERROR,
"Cannot not find matching xCallback() on "
"ProgressHandler object.");
}else{
- UNREF_G(ps->progress.jObj);
- ps->progress.midCallback = xCallback;
- ps->progress.jObj = REF_G(jProgress);
+ UNREF_G(ps->hooks.progress.jObj);
+ ps->hooks.progress.midCallback = xCallback;
+ ps->hooks.progress.jObj = REF_G(jProgress);
sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps);
}
}
@@ -2871,15 +3106,20 @@ JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){
return rc;
}
-#if S3JNI_ENABLE_AUTOEXT
-JDECL(void,1reset_1auto_1extension)(JENV_CSELF){
- if(!S3JniGlobal.autoExt.isRunning){
- while( S3JniGlobal.autoExt.pHead ){
- S3JniAutoExtension_free(env, S3JniGlobal.autoExt.pHead);
- }
+/* Clears all entries from S3JniGlobal.autoExt. */
+static void s3jni_reset_auto_extension(JNIEnv *env){
+ int i;
+ MUTEX_EXT_ENTER;
+ for( i = 0; i < SJG.autoExt.nExt; ++i ){
+ S3JniAutoExtension_clear( env, &SJG.autoExt.pExt[i] );
}
+ SJG.autoExt.nExt = 0;
+ MUTEX_EXT_LEAVE;
+}
+
+JDECL(void,1reset_1auto_1extension)(JENV_CSELF){
+ s3jni_reset_auto_extension(env);
}
-#endif /* S3JNI_ENABLE_AUTOEXT */
/* sqlite3_result_text/blob() and friends. */
static void result_blob_text(int asBlob, int as64,
@@ -2887,7 +3127,7 @@ static void result_blob_text(int asBlob, int as64,
JNIEnv * const env, sqlite3_context *pCx,
jbyteArray jBa, jlong nMax){
if(jBa){
- jbyte * const pBuf = JBA_TOC(jBa);
+ jbyte * const pBuf = s3jni_jbytearray_bytes(jBa);
jsize nBa = (*env)->GetArrayLength(env, jBa);
if( nMax>=0 && nBa>(jsize)nMax ){
nBa = (jsize)nMax;
@@ -2952,7 +3192,7 @@ static void result_blob_text(int asBlob, int as64,
break;
}
}
- JBA_RELEASE(jBa, pBuf);
+ s3jni_jbytearray_release(jBa, pBuf);
}
}else{
sqlite3_result_null(pCx);
@@ -2975,7 +3215,7 @@ JDECL(void,1result_1error)(JENV_CSELF, jobject jpCx, jbyteArray baMsg,
int eTextRep){
const char * zUnspecified = "Unspecified error.";
jsize const baLen = (*env)->GetArrayLength(env, baMsg);
- jbyte * const pjBuf = baMsg ? JBA_TOC(baMsg) : NULL;
+ 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;
@@ -2994,7 +3234,7 @@ 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){
@@ -3021,8 +3261,8 @@ 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);
+ sqlite3_result_pointer(PtrGet_sqlite3_context(jpCx), rjv,
+ ResultJavaValuePtrStr, ResultJavaVal_finalizer);
}else{
sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx));
}
@@ -3036,16 +3276,19 @@ JDECL(void,1result_1null)(JENV_CSELF, jobject 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);
+ return result_blob_text(0, 0, SQLITE_UTF8, env,
+ PtrGet_sqlite3_context(jpCx), jBa, nMax);
}
JDECL(void,1result_1text64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax,
jint eTextRep){
- return result_blob_text(0, 1, eTextRep, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
+ return result_blob_text(0, 1, eTextRep, env,
+ PtrGet_sqlite3_context(jpCx), jBa, nMax);
}
JDECL(void,1result_1value)(JENV_CSELF, jobject jpCx, jobject jpSVal){
- sqlite3_result_value(PtrGet_sqlite3_context(jpCx), PtrGet_sqlite3_value(jpSVal));
+ sqlite3_result_value(PtrGet_sqlite3_context(jpCx),
+ PtrGet_sqlite3_value(jpSVal));
}
JDECL(void,1result_1zeroblob)(JENV_CSELF, jobject jpCx, jint v){
@@ -3053,10 +3296,11 @@ JDECL(void,1result_1zeroblob)(JENV_CSELF, jobject jpCx, jint v){
}
JDECL(jint,1result_1zeroblob64)(JENV_CSELF, jobject jpCx, jlong v){
- return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v);
+ return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx),
+ (sqlite3_int64)v);
}
-JDECL(jobject,1rollback_1hook)(JENV_CSELF,jobject jDb, jobject jHook){
+JDECL(jobject,1rollback_1hook)(JENV_CSELF, jobject jDb, jobject jHook){
return s3jni_commit_rollback_hook(0, env, jDb, jHook);
}
@@ -3064,20 +3308,19 @@ JDECL(jobject,1rollback_1hook)(JENV_CSELF,jobject jDb, jobject jHook){
static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1,
const char*z2,const char*z3){
S3JniDb * const ps = pState;
- JNIEnv * const env = ps->env;
- jstring const s0 = z0 ? (*env)->NewStringUTF(env, z0) : 0;
- jstring const s1 = z1 ? (*env)->NewStringUTF(env, z1) : 0;
- jstring const s2 = z2 ? (*env)->NewStringUTF(env, z2) : 0;
- jstring const s3 = z3 ? (*env)->NewStringUTF(env, z3) : 0;
- S3JniHook const * const pHook = &ps->authHook;
+ LocalJniGetEnv;
+ S3JniHook const * const pHook = &ps->hooks.auth;
+ jstring const s0 = z0 ? s3jni_utf8_to_jstring(env, z0, -1) : 0;
+ jstring const s1 = z1 ? s3jni_utf8_to_jstring(env, z1, -1) : 0;
+ jstring const s2 = z2 ? s3jni_utf8_to_jstring(env, z2, -1) : 0;
+ jstring const s3 = z3 ? s3jni_utf8_to_jstring(env, z3, -1) : 0;
int rc;
assert( pHook->jObj );
rc = (*env)->CallIntMethod(env, pHook->jObj, pHook->midCallback, (jint)op,
s0, s1, s3, s3);
IFTHREW{
- EXCEPTION_WARN_CALLBACK_THREW("sqlite3_set_authorizer() callback");
- EXCEPTION_CLEAR;
+ rc = s3jni_db_exception(env, ps, rc, "sqlite3_set_authorizer() callback");
}
UNREF_L(s0);
UNREF_L(s1);
@@ -3087,8 +3330,8 @@ static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1,
}
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;
+ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
+ S3JniHook * const pHook = ps ? &ps->hooks.auth : 0;
if( !ps ) return SQLITE_MISUSE;
else if( !jHook ){
@@ -3096,6 +3339,7 @@ JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){
return (jint)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. */
@@ -3104,8 +3348,8 @@ JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){
S3JniHook_unref(env, pHook, 0);
}
pHook->jObj = REF_G(jHook);
- pHook->klazz = REF_G((*env)->GetObjectClass(env, jHook));
- pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz,
+ klazz = (*env)->GetObjectClass(env, jHook);
+ pHook->midCallback = (*env)->GetMethodID(env, klazz,
"xAuth",
"(I"
"Ljava/lang/String;"
@@ -3113,6 +3357,7 @@ JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){
"Ljava/lang/String;"
"Ljava/lang/String;"
")I");
+ UNREF_L(klazz);
IFTHREW {
S3JniHook_unref(env, pHook, 0);
return s3jni_db_error(ps->pDb, SQLITE_ERROR,
@@ -3130,7 +3375,6 @@ JDECL(void,1set_1last_1insert_1rowid)(JENV_CSELF, jobject jpDb, jlong rowId){
(sqlite3_int64)rowId);
}
-FIXME_THREADING(nphCache)
JDECL(jint,1status)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh,
jboolean reset ){
int iCur = 0, iHigh = 0;
@@ -3142,7 +3386,6 @@ 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 ){
sqlite3_int64 iCur = 0, iHigh = 0;
@@ -3157,18 +3400,18 @@ 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_check(pT);
/* Note that we're relying on the byte arrays having been
NUL-terminated on the Java side. */
rc = isLike
? sqlite3_strlike((const char *)pG, (const char *)pT,
(unsigned int)escLike)
: sqlite3_strglob((const char *)pG, (const char *)pT);
- JBA_RELEASE(baG, pG);
- JBA_RELEASE(baT, pT);
+ s3jni_jbytearray_release(baG, pG);
+ s3jni_jbytearray_release(baT, pT);
return rc;
}
@@ -3181,7 +3424,12 @@ JDECL(jint,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT, jint escChar){
}
JDECL(jint,1shutdown)(JENV_CSELF){
- S3JniGlobal_S3JniEnvCache_clear();
+ s3jni_reset_auto_extension(env);
+ MUTEX_ENV_ENTER;
+ while( SJG.envCache.aHead ){
+ S3JniGlobal_env_uncache( SJG.envCache.aHead->env );
+ }
+ MUTEX_ENV_LEAVE;
/* Do not clear S3JniGlobal.jvm: it's legal to call
sqlite3_initialize() again to restart the lib. */
return sqlite3_shutdown();
@@ -3192,10 +3440,9 @@ JDECL(jstring,1sql)(JENV_CSELF, jobject 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(env, zSql, -1);
+ s3jni_oom_check(rv);
}
return rv;
}
@@ -3211,22 +3458,21 @@ 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;
+ LocalJniGetEnv;
jobject jX = NULL /* the tracer's X arg */;
jobject jP = NULL /* the tracer's P arg */;
jobject jPUnref = NULL /* potentially a local ref to jP */;
- S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
int rc;
int createStmt = 0;
switch(traceflag){
case SQLITE_TRACE_STMT:
- jX = s3jni_utf8_to_jstring(jc, (const char *)pX, -1);
+ jX = s3jni_utf8_to_jstring(env, (const char *)pX, -1);
if(!jX) return SQLITE_NOMEM;
/*MARKER(("TRACE_STMT@%p SQL=%p / %s\n", pP, jX, (const char *)pX));*/
createStmt = 1;
break;
case SQLITE_TRACE_PROFILE:
- jX = (*env)->NewObject(env, jc->g.cLong, jc->g.ctorLong1,
+ 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)));
@@ -3251,13 +3497,13 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){
}
}
assert(jP);
- rc = (int)(*env)->CallIntMethod(env, ps->trace.jObj,
- ps->trace.midCallback,
+ rc = (int)(*env)->CallIntMethod(env, ps->hooks.trace.jObj,
+ ps->hooks.trace.midCallback,
(jint)traceflag, jP, jX);
IFTHREW{
EXCEPTION_WARN_CALLBACK_THREW("sqlite3_trace_v2() callback");
- EXCEPTION_CLEAR;
- rc = SQLITE_ERROR;
+ rc = s3jni_db_exception(env, ps, SQLITE_ERROR,
+ "sqlite3_trace_v2() callback threw.");
}
UNREF_L(jPUnref);
UNREF_L(jX);
@@ -3265,100 +3511,31 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){
}
JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){
- S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
+ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
jclass klazz;
+
if( !traceMask || !jTracer ){
if(ps){
- UNREF_G(ps->trace.jObj);
- memset(&ps->trace, 0, sizeof(ps->trace));
+ S3JniHook_unref(env, &ps->hooks.trace, 0);
}
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",
+ ps->hooks.trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback",
"(ILjava/lang/Object;Ljava/lang/Object;)I");
+ UNREF_L(klazz);
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);
+ ps->hooks.trace.jObj = REF_G(jTracer);
return sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps);
}
-static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb,
- const char *zTable, sqlite3_int64 nRowid){
- S3JniDb * const ps = pState;
- JNIEnv * const env = ps->env;
- /* ACHTUNG: this will break if zDb or zTable contain chars which are
- different in MUTF-8 than UTF-8. That seems like a low risk,
- but it's possible. */
- jstring jDbName;
- jstring jTable;
- jDbName = (*env)->NewStringUTF(env, zDb);
- jTable = jDbName ? (*env)->NewStringUTF(env, zTable) : 0;
- IFTHREW {
- s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
- }else{
- (*env)->CallVoidMethod(env, ps->updateHook.jObj,
- ps->updateHook.midCallback,
- (jint)opId, jDbName, jTable, (jlong)nRowid);
- IFTHREW{
- EXCEPTION_WARN_CALLBACK_THREW("update hook");
- EXCEPTION_CLEAR;
- s3jni_db_error(ps->pDb, SQLITE_ERROR, "update hook callback threw.");
- }
- }
- UNREF_L(jDbName);
- UNREF_L(jTable);
-}
-
-
JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){
- S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
- jclass klazz;
- jobject pOld = 0;
- jmethodID xCallback;
- S3JniHook * const pHook = &ps->updateHook;
- if(!ps){
- s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0);
- return 0;
- }
- pOld = pHook->jObj;
- if(pOld && jHook &&
- (*env)->IsSameObject(env, pOld, jHook)){
- return pOld;
- }
- if( !jHook ){
- if(pOld){
- jobject tmp = REF_L(pOld);
- UNREF_G(pOld);
- pOld = tmp;
- }
- memset(pHook, 0, sizeof(S3JniHook));
- sqlite3_update_hook(ps->pDb, 0, 0);
- return pOld;
- }
- klazz = (*env)->GetObjectClass(env, jHook);
- xCallback = (*env)->GetMethodID(env, klazz, "xUpdateHook",
- "(ILjava/lang/String;Ljava/lang/String;J)V");
- IFTHREW {
- EXCEPTION_CLEAR;
- s3jni_db_error(ps->pDb, SQLITE_ERROR,
- "Cannot not find matching callback on "
- "update hook object.");
- }else{
- pHook->midCallback = xCallback;
- pHook->jObj = REF_G(jHook);
- sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps);
- if(pOld){
- jobject tmp = REF_L(pOld);
- UNREF_G(pOld);
- pOld = tmp;
- }
- }
- return pOld;
+ return s3jni_updatepre_hook(env, 0, jDb, jHook);
}
@@ -3399,37 +3576,32 @@ JDECL(jlong,1value_1int64)(JENV_CSELF, jobject jpSVal){
}
JDECL(jobject,1value_1java_1object)(JENV_CSELF, jobject jpSVal){
- ResultJavaVal * const rv = sqlite3_value_pointer(PtrGet_sqlite3_value(jpSVal), RESULT_JAVA_VAL_STRING);
+ ResultJavaVal * const rv = sqlite3_value_pointer(PtrGet_sqlite3_value(jpSVal),
+ ResultJavaValuePtrStr);
return rv ? rv->jObj : NULL;
}
-JDECL(jstring,1value_1text)(JENV_CSELF, jobject jpSVal){
- sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal);
- int const n = sqlite3_value_bytes16(sv);
- const void * const p = sqlite3_value_text16(sv);
- return s3jni_text16_to_jstring(env, p, n);
-}
-
JDECL(jbyteArray,1value_1text_1utf8)(JENV_CSELF, jobject jpSVal){
sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal);
int const n = sqlite3_value_bytes(sv);
const unsigned char * const p = sqlite3_value_text(sv);
- return s3jni_new_jbyteArray(env, p, n);
+ return p ? s3jni_new_jbyteArray(env, p, n) : 0;
}
static jbyteArray value_text16(int mode, JNIEnv * const env, jobject jpSVal){
- int const nLen = sqlite3_value_bytes16(PtrGet_sqlite3_value(jpSVal));
+ sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal);
+ int const nLen = sqlite3_value_bytes16(sv);
jbyteArray jba;
const jbyte * pBytes;
switch(mode){
case SQLITE_UTF16:
- pBytes = sqlite3_value_text16(PtrGet_sqlite3_value(jpSVal));
+ pBytes = sqlite3_value_text16(sv);
break;
case SQLITE_UTF16LE:
- pBytes = sqlite3_value_text16le(PtrGet_sqlite3_value(jpSVal));
+ pBytes = sqlite3_value_text16le(sv);
break;
case SQLITE_UTF16BE:
- pBytes = sqlite3_value_text16be(PtrGet_sqlite3_value(jpSVal));
+ pBytes = sqlite3_value_text16be(sv);
break;
default:
assert(!"not possible");
@@ -3469,28 +3641,41 @@ 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(S3NphRefs);
printf("\t(^^^ %u NativePointerHolder subclasses)\n",
- (unsigned)(sizeof(S3JniClassNames) / sizeof(const char *)));
+ (unsigned)NphCache_SIZE);
SO(S3JniGlobal);
SO(S3JniAutoExtension);
SO(S3JniUdf);
printf("Cache info:\n");
- printf("\tNativePointerHolder cache: %u misses, %u hits\n",
- S3JniGlobal.metrics.nphCacheMisses,
- S3JniGlobal.metrics.nphCacheHits);
- printf("\tJNIEnv cache %u misses, %u hits\n",
- S3JniGlobal.metrics.envCacheMisses,
- S3JniGlobal.metrics.envCacheHits);
+ printf("\tJNIEnv cache: %u allocs, %u misses, %u hits\n",
+ SJG.metrics.envCacheAllocs,
+ SJG.metrics.envCacheMisses,
+ SJG.metrics.envCacheHits);
+ printf("Mutex entry:"
+ "\n\tenv %u"
+ "\n\tnph inits %u"
+ "\n\tperDb %u"
+ "\n\tautoExt %u list accesses"
+ "\n\tmetrics %u\n",
+ SJG.metrics.nMutexEnv, SJG.metrics.nMutexEnv2,
+ SJG.metrics.nMutexPerDb, SJG.metrics.nMutexAutoExt,
+ SJG.metrics.nMetrics);
+ printf("S3JniDb: %u alloced (*%u = %u bytes), %u recycled\n",
+ SJG.metrics.nPdbAlloc, (unsigned) sizeof(S3JniDb),
+ (unsigned)(SJG.metrics.nPdbAlloc * sizeof(S3JniDb)),
+ SJG.metrics.nPdbRecycled);
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);
+ SJG.metrics.nDestroy);
#undef SO
}
@@ -3517,19 +3702,17 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){
JNIEXPORT ReturnType JNICALL \
JFuncNameFtsTok(Suffix)
-#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_api)
-#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_tokenizer)
-#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Context)
-#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Tokenizer)
+#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.fts5_api)
+#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.fts5_tokenizer)
+#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.Fts5Context)
+#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.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,12 +3723,11 @@ typedef struct {
} Fts5JniAux;
static void Fts5JniAux_free(Fts5JniAux * const s){
- JNIEnv * const env = s->env;
+ LocalJniGetEnv;
if(env){
/*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/
- s3jni_call_xDestroy(env, s->jObj, s->klazz);
+ s3jni_call_xDestroy(env, s->jObj);
UNREF_G(s->jObj);
- UNREF_G(s->klazz);
UNREF_G(s->jUserData);
}
sqlite3_free(s->zFuncName);
@@ -3559,16 +3741,16 @@ static void Fts5JniAux_xDestroy(void *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";
+ 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);
+ 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");
+ UNREF_L(klazz);
IFTHREW{
EXCEPTION_REPORT;
EXCEPTION_CLEAR;
@@ -3584,10 +3766,10 @@ 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, &S3NphRefs.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, &S3NphRefs.fts5_api, sv);
}
/**
@@ -3595,13 +3777,20 @@ static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){
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, &S3NphRefs.Fts5ExtensionApi, s3jni_ftsext()
+ );
+ MUTEX_ENV_ENTER;
+ if( pNPH ){
+ if( !SJG.fts5.jFtsExt ){
+ SJG.fts5.jFtsExt = REF_G(pNPH);
+ }
+ UNREF_L(pNPH);
+ }
+ MUTEX_ENV_LEAVE;
}
- return row->jFtsExt;
+ return SJG.fts5.jFtsExt;
}
/*
@@ -3621,7 +3810,7 @@ static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){
}
JDECLFtsApi(jobject,getInstanceForDb)(JENV_CSELF,jobject jDb){
- S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0);
+ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
jobject rv = 0;
if(!ps) return 0;
else if(ps->jFtsApi){
@@ -3662,8 +3851,7 @@ 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(env, pz, pn) : 0;
if( pz ){
if( jstr ){
OutputPointer_set_String(env, jOut, jstr);
@@ -3684,9 +3872,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,14 +3882,14 @@ 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;
+ LocalJniGetEnv;
+
assert(pAux);
- env = pAux->env;
jFXA = s3jni_getFts5ExensionApi(env);
if( !jFXA ) goto error_oom;
jpFts = new_Fts5Context_wrapper(env, pFts);
@@ -3711,8 +3899,7 @@ static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi,
(*env)->CallVoidMethod(env, pAux->jObj, pAux->jmid,
jFXA, jpFts, jpCx, jArgv);
IFTHREW{
- EXCEPTION_CLEAR;
- udf_report_exception(pCx, pAux->zFuncName, "xFunction");
+ udf_report_exception(env, 1, pCx, pAux->zFuncName, "xFunction");
}
UNREF_L(jpFts);
UNREF_L(jpCx);
@@ -3732,8 +3919,9 @@ JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName,
int rc;
char const * zName;
Fts5JniAux * pAux;
+
assert(pApi);
- zName = JSTR_TOC(jName);
+ zName = s3jni_jstring_to_mutf8(jName);
if(!zName) return SQLITE_NOMEM;
pAux = Fts5JniAux_alloc(env, jFunc);
if( pAux ){
@@ -3748,23 +3936,26 @@ JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName,
pAux->zFuncName = sqlite3_mprintf("%s", zName)
/* OOM here is non-fatal. Ignore it. */;
}
- JSTR_RELEASE(jName, zName);
+ s3jni_mutf8_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){
+static void S3JniFts5AuxData_xDestroy(void *x){
if(x){
- s3jni_fts5AuxData * const p = x;
+ S3JniFts5AuxData * const p = x;
if(p->jObj){
- JNIEnv *env = p->env;
- s3jni_call_xDestroy(env, p->jObj, 0);
+ LocalJniGetEnv;
+ s3jni_call_xDestroy(env, p->jObj);
UNREF_G(p->jObj);
}
sqlite3_free(x);
@@ -3774,7 +3965,7 @@ static void s3jni_fts5AuxData_xDestroy(void *x){
JDECLFtsXA(jobject,xGetAuxdata)(JENV_OSELF,jobject jCtx, jboolean bClear){
Fts5ExtDecl;
jobject rv = 0;
- s3jni_fts5AuxData * const pAux = fext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear);
+ S3JniFts5AuxData * const pAux = fext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear);
if(pAux){
if(bClear){
if( pAux->jObj ){
@@ -3816,41 +4007,28 @@ JDECLFtsXA(jint,xPhraseCount)(JENV_OSELF,jobject jCtx){
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);
+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);
EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.a field.");
- (*env)->SetLongField(env, jIter, jc->jPhraseIter.fidB, (jlong)pSrc->b);
+ (*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidB, (jlong)pSrc->b);
EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.b field.");
}
/* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */
-static void s3jni_phraseIter_JToN(JNIEnv *const env, S3JniEnvCache const * const jc,
- jobject jIter, Fts5PhraseIter * const pDest){
- assert(jc->jPhraseIter.klazz);
+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);
+ (const unsigned char *)(*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidA);
EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field.");
pDest->b =
- (const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidB);
+ (const unsigned char *)(*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidB);
EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field.");
}
@@ -3858,16 +4036,14 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase,
jobject jIter, jobject jOutCol,
jobject jOutOff){
Fts5ExtDecl;
- S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
Fts5PhraseIter iter;
int rc, iCol = 0, iOff = 0;
- s3jni_phraseIter_init(env, jc, jIter);
rc = fext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase,
&iter, &iCol, &iOff);
if( 0==rc ){
OutputPointer_set_Int32(env, jOutCol, iCol);
OutputPointer_set_Int32(env, jOutOff, iOff);
- s3jni_phraseIter_NToJ(env, jc, &iter, jIter);
+ s3jni_phraseIter_NToJ(env, &iter, jIter);
}
return rc;
}
@@ -3875,15 +4051,13 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase,
JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase,
jobject jIter, jobject jOutCol){
Fts5ExtDecl;
- S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
Fts5PhraseIter iter;
int rc, iCol = 0;
- s3jni_phraseIter_init(env, jc, jIter);
rc = fext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase,
&iter, &iCol);
if( 0==rc ){
OutputPointer_set_Int32(env, jOutCol, iCol);
- s3jni_phraseIter_NToJ(env, jc, &iter, jIter);
+ s3jni_phraseIter_NToJ(env, &iter, jIter);
}
return rc;
}
@@ -3891,29 +4065,24 @@ JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase,
JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter,
jobject jOutCol, jobject jOutOff){
Fts5ExtDecl;
- S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
Fts5PhraseIter iter;
int iCol = 0, iOff = 0;
- if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/;
- s3jni_phraseIter_JToN(env, jc, jIter, &iter);
- fext->xPhraseNext(PtrGet_Fts5Context(jCtx),
- &iter, &iCol, &iOff);
+ 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,
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);
}
@@ -3922,13 +4091,10 @@ JDECLFtsXA(jint,xPhraseSize)(JENV_OSELF,jobject jCtx, jint iPhrase){
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,11 +4112,11 @@ 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;
+ LocalJniGetEnv;
int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback,
- s->jc->jFtsExt, s->jFcx);
+ SJG.fts5.jFtsExt, s->jFcx);
IFTHREW{
- EXCEPTION_WARN_CALLBACK_THREW("xQueryPhrase callback");
+ EXCEPTION_WARN_CALLBACK_THREW("xQueryPhrase() callback");
EXCEPTION_CLEAR;
rc = SQLITE_ERROR;
}
@@ -3960,11 +4126,11 @@ static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi,
JDECLFtsXA(jint,xQueryPhrase)(JENV_OSELF,jobject jFcx, jint iPhrase,
jobject jCallback){
Fts5ExtDecl;
- S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
+ S3JniEnv * const jc = S3JniGlobal_env_cache(env);
struct s3jni_xQueryPhraseState s;
jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL;
+
if( !klazz ) return SQLITE_MISUSE;
- s.env = env;
s.jc = jc;
s.jCallback = jCallback;
s.jFcx = jFcx;
@@ -3972,6 +4138,7 @@ 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");
+ UNREF_L(klazz);
EXCEPTION_IS_FATAL("Could not extract xQueryPhraseCallback.xCallback method.");
return (jint)fext->xQueryPhrase(PtrGet_Fts5Context(jFcx), iPhrase, &s,
s3jni_xQueryPhrase);
@@ -3994,31 +4161,28 @@ JDECLFtsXA(jlong,xRowid)(JENV_OSELF,jobject jCtx){
JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){
Fts5ExtDecl;
int rc;
- s3jni_fts5AuxData * pAux;
+ S3JniFts5AuxData * 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);
+ /* 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);
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;
+ LocalJniGetEnv;
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;
@@ -4039,41 +4203,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(JENV_OSELF, S3NphRef const *pRef,
jint tokFlags, jobject jFcx,
jbyteArray jbaText, jobject jCallback){
Fts5ExtDecl;
- S3JniEnvCache * const jc = S3JniGlobal_env_cache(env);
+ S3JniEnv * const jc = S3JniGlobal_env_cache(env);
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");
+ UNREF_L(klazz);
IFTHREW {
EXCEPTION_REPORT;
EXCEPTION_CLEAR;
- JBA_RELEASE(jbaText, pText);
+ s3jni_jbytearray_release(jbaText, pText);
return SQLITE_ERROR;
}
s.tok.jba = REF_L(jbaText);
s.tok.zPrev = (const char *)pText;
s.tok.nPrev = (int)nText;
- if( zClassName == S3JniClassNames.Fts5ExtensionApi ){
+ if( pRef == &S3NphRefs.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 == &S3NphRefs.fts5_tokenizer ){
fts5_tokenizer * const pTok = PtrGet_fts5_tokenizer(jSelf);
rc = pTok->xTokenize(PtrGet_Fts5Tokenizer(jFcx), &s, tokFlags,
(const char *)pText, (int)nText,
@@ -4085,19 +4251,19 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName,
assert( s.tok.zPrev );
UNREF_L(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,
+ return s3jni_fts5_xTokenize(env, jSelf, &S3NphRefs.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,
+ return s3jni_fts5_xTokenize(env, jSelf, &S3NphRefs.Fts5Tokenizer,
tokFlags, jFcx, jbaText, jCallback);
}
@@ -4283,15 +4449,15 @@ Java_org_sqlite_jni_tester_SQLTester_strglob(
JENV_CSELF, jbyteArray baG, jbyteArray baT
){
int rc = 0;
- jbyte * const pG = JBA_TOC(baG);
- jbyte * const pT = pG ? JBA_TOC(baT) : 0;
- OOM_CHECK(pT);
+ jbyte * const pG = s3jni_jbytearray_bytes(baG);
+ jbyte * const pT = pG ? s3jni_jbytearray_bytes(baT) : 0;
+ s3jni_oom_check(pT);
/* Note that we're relying on the byte arrays having been
NUL-terminated on the Java side. */
rc = !SQLTester_strnotglob((const char *)pG, (const char *)pT);
- JBA_RELEASE(baG, pG);
- JBA_RELEASE(baT, pT);
+ s3jni_jbytearray_release(baG, pG);
+ s3jni_jbytearray_release(baT, pT);
return rc;
}
@@ -4316,23 +4482,27 @@ Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JENV_CSELF){
////////////////////////////////////////////////////////////////////////
-/**
- 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.
+/*
+** 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;
+ int rc;
+ MUTEX_ENV_ENTER;
+ rc = S3JniGlobal_env_uncache(env);
+ MUTEX_ENV_LEAVE;
+ return rc ? 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){
@@ -4346,13 +4516,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},
@@ -4381,30 +4544,84 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){
{0,0}
};
jfieldID fieldId;
+ jclass klazz;
const ConfigFlagEntry * pConfFlag;
+ if( 0==sqlite3_threadsafe() ){
+ (*env)->FatalError(env, "sqlite3 was not built with SQLITE_THREADSAFE.");
+ return;
+ }
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.cObj = REF_G((*env)->FindClass(env,"java/lang/Object"));
+ EXCEPTION_IS_FATAL("Error getting reference to Object class.");
+
+ SJG.g.cLong = REF_G((*env)->FindClass(env,"java/lang/Long"));
+ EXCEPTION_IS_FATAL("Error getting reference to Long class.");
+ SJG.g.ctorLong1 = (*env)->GetMethodID(env, SJG.g.cLong,
+ "", "(J)V");
+ EXCEPTION_IS_FATAL("Error getting reference to Long constructor.");
+
+ SJG.g.cString = REF_G((*env)->FindClass(env,"java/lang/String"));
+ EXCEPTION_IS_FATAL("Error getting reference to String class.");
+ SJG.g.ctorStringBA =
+ (*env)->GetMethodID(env, SJG.g.cString,
+ "", "([BLjava/nio/charset/Charset;)V");
+ EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor.");
+ SJG.g.stringGetBytes =
+ (*env)->GetMethodID(env, SJG.g.cString,
+ "getBytes", "(Ljava/nio/charset/Charset;)[B");
+ EXCEPTION_IS_FATAL("Error getting reference to String.getBytes(Charset).");
+
+ { /* StandardCharsets.UTF_8 */
+ jfieldID fUtf8;
+ klazz = (*env)->FindClass(env,"java/nio/charset/StandardCharsets");
+ EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets class.");
+ fUtf8 = (*env)->GetStaticFieldID(env, klazz, "UTF_8",
+ "Ljava/nio/charset/Charset;");
+ EXCEPTION_IS_FATAL("Error getting StandardCharsets.UTF_8 field.");
+ SJG.g.oCharsetUtf8 =
+ REF_G((*env)->GetStaticObjectField(env, klazz, fUtf8));
+ EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8.");
+ UNREF_L(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");
+ EXCEPTION_IS_FATAL("Error getting reference to org.sqlite.jni.Fts5PhraseIter.");
+ SJG.fts5.jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J");
+ EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field.");
+ SJG.fts5.jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "b", "J");
+ EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field.");
+ UNREF_L(klazz);
#endif
+ SJG.envCache.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_check( SJG.envCache.mutex );
+ SJG.perDb.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_check( SJG.perDb.mutex );
+ SJG.autoExt.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_check( SJG.autoExt.mutex );
+
+#if S3JNI_METRICS_MUTEX
+ SJG.metrics.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_check( 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));
assert(fieldId);
switch(pConfFlag->jtype){
case JTYPE_INT:
diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h
index bcb55c4f1c..8052a6027e 100644
--- a/ext/jni/src/c/sqlite3-jni.h
+++ b/ext/jni/src/c/sqlite3-jni.h
@@ -843,6 +843,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1parameter_1
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1text
(JNIEnv *, jclass, jobject, jint, jbyteArray, jint);
+/*
+ * Class: org_sqlite_jni_SQLite3Jni
+ * Method: sqlite3_bind_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
@@ -1013,18 +1021,18 @@ 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
+ * Method: sqlite3_column_text16
+ * 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_1text16
(JNIEnv *, jclass, jobject, jint);
/*
@@ -1083,6 +1091,22 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption_
JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption_1used
(JNIEnv *, jclass, jstring);
+/*
+ * 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/SQLLog;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1config__Lorg_sqlite_jni_SQLLog_2
+ (JNIEnv *, jclass, jobject);
+
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_create_collation
@@ -1211,6 +1235,22 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1finalize
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1initialize
(JNIEnv *, jclass);
+/*
+ * 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
@@ -1275,6 +1315,54 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v2
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v3
(JNIEnv *, jclass, jobject, jbyteArray, jint, jint, jobject, jobject);
+/*
+ * Class: org_sqlite_jni_SQLite3Jni
+ * Method: sqlite3_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/PreUpdateHook;)Lorg/sqlite/jni/PreUpdateHook;
+ */
+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
@@ -1427,6 +1515,14 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text
JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text64
(JNIEnv *, jclass, jobject, jbyteArray, jlong, jint);
+/*
+ * 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_status
@@ -1635,14 +1731,6 @@ JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1int64
JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1java_1object
(JNIEnv *, jclass, jobject);
-/*
- * Class: org_sqlite_jni_SQLite3Jni
- * Method: sqlite3_value_text
- * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/String;
- */
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text
- (JNIEnv *, jclass, jobject);
-
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_value_text_utf8
@@ -1715,14 +1803,6 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1frombind
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1subtype
(JNIEnv *, jclass, jobject);
-/*
- * Class: org_sqlite_jni_SQLite3Jni
- * Method: sqlite3_shutdown
- * Signature: ()I
- */
-JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1shutdown
- (JNIEnv *, jclass);
-
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_do_something_for_developer
diff --git a/ext/jni/src/org/sqlite/jni/AutoExtension.java b/ext/jni/src/org/sqlite/jni/AutoExtension.java
index a2ab6a0f75..443345fde4 100644
--- a/ext/jni/src/org/sqlite/jni/AutoExtension.java
+++ b/ext/jni/src/org/sqlite/jni/AutoExtension.java
@@ -14,18 +14,27 @@
package org.sqlite.jni;
/**
- A callback for use with sqlite3_auto_extension().
+ A callback for use with the sqlite3_auto_extension() family of
+ APIs.
*/
public interface AutoExtension {
/**
- Must function as described for the sqlite3_auto_extension(),
- with the caveat that the signature is more limited.
+ Must function as described for a sqlite3_auto_extension()
+ callback, with the caveat that the signature is shorter.
- As an exception (as it were) to the callbacks-must-not-throw
- rule, AutoExtensions may do so and the exception's error message
+ AutoExtensions may throw 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.
+ Tips for implementations:
+
+ - 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.
+
+ - If this routine is stateful, it may be useful to make the
+ overridden method synchronized.
+
+ - Results are undefined if db is closed by an auto-extension.
*/
int xEntryPoint(sqlite3 db);
}
diff --git a/ext/jni/src/org/sqlite/jni/Fts5.java b/ext/jni/src/org/sqlite/jni/Fts5.java
index 102cf575a8..443a69a409 100644
--- a/ext/jni/src/org/sqlite/jni/Fts5.java
+++ b/ext/jni/src/org/sqlite/jni/Fts5.java
@@ -27,7 +27,7 @@ public final class Fts5 {
//! Callback type for use with xTokenize() variants
public static interface xTokenizeCallback {
- int xToken(int tFlags, byte txt[], int iStart, int iEnd);
+ int xToken(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 ac041e3001..205f110f41 100644
--- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java
+++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java
@@ -23,7 +23,7 @@ import java.nio.charset.StandardCharsets;
public final class Fts5ExtensionApi extends NativePointerHolder {
//! 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 +33,54 @@ public final class Fts5ExtensionApi extends NativePointerHolder {
//! 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 82a90c9185..bf61656dd5 100644
--- a/ext/jni/src/org/sqlite/jni/OutputPointer.java
+++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java
@@ -36,6 +36,9 @@ package org.sqlite.jni;
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.
+
+ Warning: do not share instances of these classes across
+ threads. Doing so may lead to corrupting sqlite3-internal state.
*/
public final class OutputPointer {
@@ -83,6 +86,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.
diff --git a/ext/jni/src/org/sqlite/jni/PreUpdateHook.java b/ext/jni/src/org/sqlite/jni/PreUpdateHook.java
new file mode 100644
index 0000000000..d5d82c72bc
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/PreUpdateHook.java
@@ -0,0 +1,29 @@
+/*
+** 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_preupdate_hook().
+*/
+public interface PreUpdateHook {
+ /**
+ Must function as described for the sqlite3_preupdate_hook().
+ callback, with the slight signature change.
+
+ Must not throw. Any exceptions may emit debugging messages and
+ will be suppressed.
+ */
+ void xPreUpdate(sqlite3 db, int op, String dbName, String dbTable,
+ long iKey1, long iKey2 );
+}
diff --git a/ext/jni/src/org/sqlite/jni/SQLFunction.java b/ext/jni/src/org/sqlite/jni/SQLFunction.java
index 21e5fe622a..c8e87ff827 100644
--- a/ext/jni/src/org/sqlite/jni/SQLFunction.java
+++ b/ext/jni/src/org/sqlite/jni/SQLFunction.java
@@ -21,17 +21,19 @@ package org.sqlite.jni;
This class is not used by itself, but is a marker base class. The
three UDF types are modelled by the inner classes Scalar,
- Aggregate, and Window. Most simply, clients may create
- anonymous classes from those to implement UDFs. Clients are free to
- create their own classes for use with UDFs, so long as they conform
- to the public interfaces defined by those three classes. The JNI
- layer only actively relies on the SQLFunction base class.
+ Aggregate, and Window. 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 {
/**
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
@@ -96,7 +98,11 @@ public abstract class SQLFunction {
//! 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()
+ /**
+ 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);
/**
@@ -114,10 +120,18 @@ public abstract class SQLFunction {
*/
public static abstract class Aggregate extends SQLFunction {
- //! As for the xStep() argument of the C API's sqlite3_create_function()
+ /**
+ 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()
+ /**
+ 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);
/**
@@ -163,10 +177,18 @@ public abstract class SQLFunction {
*/
public static abstract class Window extends Aggregate {
- //! As for the xInverse() argument of the C API's sqlite3_create_window_function()
+ /**
+ 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()
+ /**
+ 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/SQLLog.java b/ext/jni/src/org/sqlite/jni/SQLLog.java
new file mode 100644
index 0000000000..c1bc0aab61
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/SQLLog.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(SQLLog).
+*/
+public interface SQLLog {
+ /**
+ Must function as described for sqlite3_config(SQLITE_CONFIG_SQLLOG)
+ callback, with the slight signature change.
+ */
+ void xSqllog(sqlite3 db, String msg, int msgType );
+}
diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java
index 9b2a176504..ef67890918 100644
--- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java
+++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java
@@ -82,27 +82,16 @@ import java.lang.annotation.ElementType;
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).
+ - Any functions which return state from a database take extra care
+ to perform proper conversion, at the cost of efficiency.
- 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.
+ this so long as the length is provided. 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
@@ -133,21 +122,13 @@ public final class SQLite3Jni {
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.
+ library will re-initialize the cache on demand.
- 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.
+ 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.
Calling this from the main application thread is not strictly
required but is "polite." Additional threads must call this
@@ -173,67 +154,59 @@ public final class SQLite3Jni {
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.
+ - 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.
+ The C API docs do not specifically say so, 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.
-
- Achtung: it is as yet unknown whether auto extensions registered
- from one JNIEnv (thread) can be safely called from another.
*/
- public static synchronized native int sqlite3_auto_extension(@NotNull AutoExtension callback);
+ public static native int sqlite3_auto_extension(@NotNull AutoExtension callback);
+
+ /**
+ Results are undefined if data is not null and n<0 || n>=data.length.
+ */
+ 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(
+ public static native int sqlite3_bind_double(
@NotNull sqlite3_stmt stmt, int ndx, double v
);
- public static synchronized native int sqlite3_bind_int(
+ public static native int sqlite3_bind_int(
@NotNull sqlite3_stmt stmt, int ndx, int v
);
- public static synchronized native int sqlite3_bind_int64(
+ public static native int sqlite3_bind_int64(
@NotNull sqlite3_stmt stmt, int ndx, long v
);
- public static synchronized native int sqlite3_bind_null(
+ public static native int sqlite3_bind_null(
@NotNull sqlite3_stmt stmt, int ndx
);
- public static synchronized native int sqlite3_bind_parameter_count(
+ 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.
+ */
+ public static native int sqlite3_bind_parameter_index(
@NotNull sqlite3_stmt stmt, byte[] paramName
);
@@ -244,6 +217,23 @@ 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.
+
+ Results are undefined if data is not null and
+ maxBytes>=data.length. If maxBytes is negative then results are
+ undefined if data is not null and does not contain a NUL byte.
+ */
+ private static native int sqlite3_bind_text(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, 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,6 +242,9 @@ public final class SQLite3Jni {
return sqlite3_bind_text(stmt, ndx, utf8, utf8.length);
}
+ /**
+ Requires that data be null or in UTF-8 encoding.
+ */
public static int sqlite3_bind_text(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
){
@@ -261,98 +254,118 @@ public final class SQLite3Jni {
}
/**
- 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(
+ private 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_text().
+ */
+ 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);
+ }
+
+ public static native int sqlite3_bind_zeroblob(
@NotNull sqlite3_stmt stmt, int ndx, int n
);
- public static synchronized native int sqlite3_bind_zeroblob64(
+ 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
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(
+ public static native int sqlite3_busy_handler(
@NotNull sqlite3 db, @Nullable BusyHandler handler
);
- public static synchronized native int sqlite3_busy_timeout(
+ 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(
+ public static native boolean sqlite3_cancel_auto_extension(
@NotNull AutoExtension ax
);
- public static synchronized native int sqlite3_changes(
+ public static native int sqlite3_changes(
@NotNull sqlite3 db
);
- public static synchronized native long sqlite3_changes64(
+ public static native long sqlite3_changes64(
@NotNull sqlite3 db
);
- public static synchronized native int sqlite3_clear_bindings(
+ public static native int sqlite3_clear_bindings(
@NotNull sqlite3_stmt stmt
);
- public static synchronized native int sqlite3_close(
- @NotNull sqlite3 db
+ public static native int sqlite3_close(
+ @Nullable sqlite3 db
);
- public static synchronized native int sqlite3_close_v2(
- @NotNull sqlite3 db
+ public static native int sqlite3_close_v2(
+ @Nullable sqlite3 db
);
- public static synchronized native byte[] sqlite3_column_blob(
+ public static native byte[] sqlite3_column_blob(
@NotNull sqlite3_stmt stmt, int ndx
);
- public static synchronized native int sqlite3_column_bytes(
+ public static native int sqlite3_column_bytes(
@NotNull sqlite3_stmt stmt, int ndx
);
- public static synchronized native int sqlite3_column_bytes16(
+ public static native int sqlite3_column_bytes16(
@NotNull sqlite3_stmt stmt, int ndx
);
- public static synchronized native int sqlite3_column_count(
+ public static native int sqlite3_column_count(
@NotNull sqlite3_stmt stmt
);
- public static synchronized native double sqlite3_column_double(
+ public static native double sqlite3_column_double(
@NotNull sqlite3_stmt stmt, int ndx
);
- public static synchronized native int sqlite3_column_int(
+ public static native int sqlite3_column_int(
@NotNull sqlite3_stmt stmt, int ndx
);
- public static synchronized native long sqlite3_column_int64(
+ public static native long sqlite3_column_int64(
@NotNull sqlite3_stmt stmt, int ndx
);
- public static synchronized native String sqlite3_column_name(
+ public static native String sqlite3_column_name(
@NotNull sqlite3_stmt stmt, int ndx
);
- public static synchronized native String sqlite3_column_database_name(
+ public static native String sqlite3_column_database_name(
@NotNull sqlite3_stmt stmt, int ndx
);
@@ -385,28 +398,31 @@ public final class SQLite3Jni {
return type.isInstance(o) ? (T)o : null;
}
- public static synchronized native String sqlite3_column_origin_name(
+ public static native String sqlite3_column_origin_name(
@NotNull sqlite3_stmt stmt, int ndx
);
- public static synchronized native String sqlite3_column_table_name(
+ 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.
*/
- public static synchronized native String sqlite3_column_text16(
+ 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
- */
- public static synchronized native byte[] sqlite3_column_text(
+ public static String sqlite3_column_text(
+ @NotNull sqlite3_stmt stmt, int ndx
+ ){
+ final byte[] ba = sqlite3_column_text_utf8(stmt, ndx);
+ return ba==null ? null : new String(ba, StandardCharsets.UTF_8);
+ }
+
+ public static native String sqlite3_column_text16(
@NotNull sqlite3_stmt stmt, int ndx
);
@@ -447,11 +463,11 @@ public final class SQLite3Jni {
// return rv;
// }
- public static synchronized native int sqlite3_column_type(
+ public static native int sqlite3_column_type(
@NotNull sqlite3_stmt stmt, int ndx
);
- public static synchronized native sqlite3_value sqlite3_column_value(
+ public static native sqlite3_value sqlite3_column_value(
@NotNull sqlite3_stmt stmt, int ndx
);
@@ -459,7 +475,7 @@ 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(
+ public static native int sqlite3_collation_needed(
@NotNull sqlite3 db, @Nullable CollationNeeded callback
);
@@ -467,11 +483,11 @@ public final class SQLite3Jni {
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(
+ public static native sqlite3 sqlite3_context_db_handle(
@NotNull sqlite3_context cx
);
- public static synchronized native CommitHook sqlite3_commit_hook(
+ public static native CommitHook sqlite3_commit_hook(
@NotNull sqlite3 db, @Nullable CommitHook hook
);
@@ -483,7 +499,32 @@ public final class SQLite3Jni {
@NotNull String optName
);
- public static synchronized native int sqlite3_create_collation(
+ /*
+ ** Works like in the C API with the exception that it only supports
+ ** the following subset of configution flags:
+ **
+ ** - SQLITE_CONFIG_SINGLETHREAD
+ ** - SQLITE_CONFIG_MULTITHREAD
+ ** - SQLITE_CONFIG_SERIALIZED
+ **
+ ** Others may be added in the future. It returns SQLITE_MISUSE if
+ ** given an argument it does not handle.
+ */
+ 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.
+ **
+ ** If not built with SQLITE_ENABLE_SQLLOG defined, this returns
+ ** SQLITE_MISUSE.
+ */
+ public static native int sqlite3_config( @Nullable SQLLog logger );
+
+ public static native int sqlite3_create_collation(
@NotNull sqlite3 db, @NotNull String name, int eTextRep,
@NotNull Collation col
);
@@ -496,16 +537,16 @@ public final class SQLite3Jni {
SQLFunction's inner classes (Scalar, Aggregate, and Window)
for details.
*/
- public static synchronized native int sqlite3_create_function(
+ 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(
+ public static native int sqlite3_data_count(
@NotNull sqlite3_stmt stmt
);
- public static synchronized native String sqlite3_db_filename(
+ public static native String sqlite3_db_filename(
@NotNull sqlite3 db, @NotNull String dbName
);
@@ -514,7 +555,7 @@ public final class SQLite3Jni {
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(
+ public static native int sqlite3_db_config(
@NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out
);
@@ -525,43 +566,62 @@ 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(
+ public static native int sqlite3_db_config(
@NotNull sqlite3 db, int op, @NotNull String val
);
- public static synchronized native int sqlite3_db_status(
+ 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);
+ public static native int sqlite3_errcode(@NotNull sqlite3 db);
- public static synchronized native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt);
+ public static native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt);
- public static synchronized native int sqlite3_extended_errcode(@NotNull sqlite3 db);
+ public static native int sqlite3_extended_errcode(@NotNull sqlite3 db);
- public static synchronized native boolean sqlite3_extended_result_codes(
+ public static native boolean sqlite3_extended_result_codes(
@NotNull sqlite3 db, boolean onoff
);
- public static synchronized native String sqlite3_errmsg(@NotNull sqlite3 db);
+ public static native String sqlite3_errmsg(@NotNull sqlite3 db);
- public static synchronized native String sqlite3_errstr(int resultCode);
+ public static native String sqlite3_errstr(int resultCode);
/**
Note that the offset values assume UTF-8-encoded SQL.
*/
- public static synchronized native int sqlite3_error_offset(@NotNull sqlite3 db);
+ public static native int sqlite3_error_offset(@NotNull sqlite3 db);
- public static synchronized native int sqlite3_finalize(@NotNull sqlite3_stmt stmt);
+ public static native int sqlite3_finalize(@NotNull sqlite3_stmt stmt);
- public static synchronized native int sqlite3_initialize();
+ public static native int sqlite3_initialize();
- public static synchronized native long sqlite3_last_insert_rowid(@NotNull sqlite3 db);
+ /**
+ Design note/FIXME: we have a problem vis-a-vis 'synchronized'
+ here: we specifically want other threads to be able to cancel a
+ long-running thread, but this routine requires access to C-side
+ global state which does not have a mutex. Making this function
+ synchronized would make it impossible for a long-running job to
+ be cancelled from another thread.
- public static synchronized native String sqlite3_libversion();
+ The mutexing problem here is not within the core lib or Java, but
+ within the cached data held by the JNI binding. The cache holds
+ per-thread state, used by all but a tiny fraction of the JNI
+ binding layer, and access to that state needs to be
+ mutex-protected.
+ */
+ public static native void sqlite3_interrupt(@NotNull sqlite3 db);
- public static synchronized native int sqlite3_libversion_number();
+ //! See sqlite3_interrupt() for threading concerns.
+ public static native boolean sqlite3_is_interrupted(@NotNull sqlite3 db);
+
+ public static native long sqlite3_last_insert_rowid(@NotNull sqlite3 db);
+
+ public static native String sqlite3_libversion();
+
+ public static native int sqlite3_libversion_number();
/**
Works like its C counterpart and makes the native pointer of the
@@ -575,26 +635,46 @@ public final class SQLite3Jni {
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(
+ 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.
+
+ Ownership of the returned value is passed to the caller, who must eventually
+ pass it to sqlite3_close() or sqlite3_close_v2().
+ */
+ public static sqlite3 sqlite3_open(@Nullable String filename){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ sqlite3_open(filename, out);
+ return out.take();
+ };
+
+ public static native int sqlite3_open_v2(
@Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb,
int flags, @Nullable String zVfs
);
+ /**
+ Has the same semantics as the sqlite3-returning sqlite3_open()
+ but uses sqlite3_open_v2() instead of sqlite3_open().
+ */
+ public static sqlite3 sqlite3_open_v2(@Nullable String filename, int flags,
+ @Nullable String zVfs){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ sqlite3_open_v2(filename, out, flags, zVfs);
+ return out.take();
+ };
+
/**
The sqlite3_prepare() family of functions require slightly
- different signatures than their native counterparts, but
- 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
that it be in UTF-8 encoding unless explicitly noted otherwise.
@@ -610,7 +690,7 @@ public final class SQLite3Jni {
necessary, however, and overloads are provided which gloss over
that.
*/
- private static synchronized native int sqlite3_prepare(
+ private static native int sqlite3_prepare(
@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes,
@NotNull OutputPointer.sqlite3_stmt outStmt,
@Nullable OutputPointer.Int32 pTailOffset
@@ -639,7 +719,27 @@ 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() for details about the slight API differences
+ from the C API.
+ */
+ private static native int sqlite3_prepare_v2(
@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes,
@NotNull OutputPointer.sqlite3_stmt outStmt,
@Nullable OutputPointer.Int32 pTailOffset
@@ -668,7 +768,19 @@ 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();
+ }
+
+ 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
@@ -697,22 +809,97 @@ public final class SQLite3Jni {
return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null);
}
- public static synchronized native void sqlite3_progress_handler(
+ /**
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ public static native PreUpdateHook sqlite3_preupdate_hook(@NotNull sqlite3 db,
+ @Nullable PreUpdateHook hook);
+
+ /**
+ 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.
+ */
+ public static native int sqlite3_preupdate_new(@NotNull sqlite3 db, int col,
+ @NotNull OutputPointer.sqlite3_value out);
+
+ /**
+ Convenience wrapper for the 3-arg sqlite3_preupdate_new() which returns
+ null on error.
+ */
+ public static sqlite3_value sqlite3_preupdate_new(@NotNull sqlite3 db, int col){
+ final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value();
+ sqlite3_preupdate_new(db, 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.
+ */
+ 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();
+ }
+
+ public static native void sqlite3_progress_handler(
@NotNull sqlite3 db, int n, @Nullable ProgressHandler h
);
//TODO??? void *sqlite3_preupdate_hook(...) and friends
- public static synchronized native int sqlite3_reset(@NotNull sqlite3_stmt stmt);
+ 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();
+ public static native void sqlite3_reset_auto_extension();
- public static synchronized native void sqlite3_result_double(
+ public static native void sqlite3_result_double(
@NotNull sqlite3_context cx, double v
);
@@ -723,7 +910,7 @@ 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(
+ private static native void sqlite3_result_error(
@NotNull sqlite3_context cx, @Nullable byte[] msg,
int eTextRep
);
@@ -766,27 +953,27 @@ public final class SQLite3Jni {
sqlite3_result_error16(cx, e.getMessage());
}
- public static synchronized native void sqlite3_result_error_toobig(
+ public static native void sqlite3_result_error_toobig(
@NotNull sqlite3_context cx
);
- public static synchronized native void sqlite3_result_error_nomem(
+ public static native void sqlite3_result_error_nomem(
@NotNull sqlite3_context cx
);
- public static synchronized native void sqlite3_result_error_code(
+ public static native void sqlite3_result_error_code(
@NotNull sqlite3_context cx, int c
);
- public static synchronized native void sqlite3_result_null(
+ public static native void sqlite3_result_null(
@NotNull sqlite3_context cx
);
- public static synchronized native void sqlite3_result_int(
+ public static native void sqlite3_result_int(
@NotNull sqlite3_context cx, int v
);
- public static synchronized native void sqlite3_result_int64(
+ public static native void sqlite3_result_int64(
@NotNull sqlite3_context cx, long v
);
@@ -806,7 +993,7 @@ public final class SQLite3Jni {
Note that there is no sqlite3_bind_java_object() counterpart.
*/
- public static synchronized native void sqlite3_result_java_object(
+ public static native void sqlite3_result_java_object(
@NotNull sqlite3_context cx, @NotNull Object o
);
@@ -864,19 +1051,19 @@ public final class SQLite3Jni {
sqlite3_result_text(cx, v);
}
- public static synchronized native void sqlite3_result_value(
+ public static native void sqlite3_result_value(
@NotNull sqlite3_context cx, @NotNull sqlite3_value v
);
- public static synchronized native void sqlite3_result_zeroblob(
+ public static native void sqlite3_result_zeroblob(
@NotNull sqlite3_context cx, int n
);
- public static synchronized native int sqlite3_result_zeroblob64(
+ public static native int sqlite3_result_zeroblob64(
@NotNull sqlite3_context cx, long n
);
- private static synchronized native void sqlite3_result_blob(
+ private static native void sqlite3_result_blob(
@NotNull sqlite3_context cx, @Nullable byte[] blob, int maxLen
);
@@ -896,7 +1083,7 @@ public final class SQLite3Jni {
If maxLen is larger than blob.length, it is truncated to that
value. If it is negative, results are undefined.
*/
- private static synchronized native void sqlite3_result_blob64(
+ private static native void sqlite3_result_blob64(
@NotNull sqlite3_context cx, @Nullable byte[] blob, long maxLen
);
@@ -906,7 +1093,7 @@ public final class SQLite3Jni {
sqlite3_result_blob64(cx, blob, (long)(null==blob ? 0 : blob.length));
}
- private static synchronized native void sqlite3_result_text(
+ private static native void sqlite3_result_text(
@NotNull sqlite3_context cx, @Nullable byte[] text, int maxLen
);
@@ -938,19 +1125,27 @@ public final class SQLite3Jni {
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 following
+ arguments are ignored.
*/
- private static synchronized native void sqlite3_result_text64(
+ 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(
+ /**
+ Cleans up all per-JNIEnv and per-db state managed by the library,
+ as well as any registered auto-extensions, then calls the
+ C-native sqlite3_shutdown().
+ */
+ public static synchronized native int sqlite3_shutdown();
+
+ public static native int sqlite3_status(
int op, @NotNull OutputPointer.Int32 pCurrent,
@NotNull OutputPointer.Int32 pHighwater, boolean reset
);
- public static synchronized native int sqlite3_status64(
+ public static native int sqlite3_status64(
int op, @NotNull OutputPointer.Int64 pCurrent,
@NotNull OutputPointer.Int64 pHighwater, boolean reset
);
@@ -1006,32 +1201,32 @@ public final class SQLite3Jni {
sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16BE);
}
- public static synchronized native RollbackHook sqlite3_rollback_hook(
+ public static native RollbackHook sqlite3_rollback_hook(
@NotNull sqlite3 db, @Nullable RollbackHook hook
);
//! Sets or unsets (if auth is null) the current authorizer.
- public static synchronized native int sqlite3_set_authorizer(
+ public static native int sqlite3_set_authorizer(
@NotNull sqlite3 db, @Nullable Authorizer auth
);
- public static synchronized native void sqlite3_set_last_insert_rowid(
+ 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 native int sqlite3_sleep(int ms);
- public static synchronized native String sqlite3_sourceid();
+ public static native String sqlite3_sourceid();
- public static synchronized native String sqlite3_sql(@NotNull sqlite3_stmt stmt);
+ public static native String sqlite3_sql(@NotNull sqlite3_stmt stmt);
- public static synchronized native int sqlite3_step(@NotNull sqlite3_stmt stmt);
+ 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.
*/
- private static synchronized native int sqlite3_strglob(
+ private static native int sqlite3_strglob(
@NotNull byte[] glob, @NotNull byte[] txt
);
@@ -1048,7 +1243,7 @@ public final class SQLite3Jni {
Internal impl of the public sqlite3_strlike() method. Neither
argument may be NULL and both _MUST_ be NUL-terminated.
*/
- private static synchronized native int sqlite3_strlike(
+ private static native int sqlite3_strlike(
@NotNull byte[] glob, @NotNull byte[] txt, int escChar
);
@@ -1062,11 +1257,11 @@ public final class SQLite3Jni {
);
}
- public static synchronized native int sqlite3_threadsafe();
+ public static native int sqlite3_threadsafe();
- public static synchronized native int sqlite3_total_changes(@NotNull sqlite3 db);
+ public static native int sqlite3_total_changes(@NotNull sqlite3 db);
- public static synchronized native long sqlite3_total_changes64(@NotNull sqlite3 db);
+ public static native long sqlite3_total_changes64(@NotNull sqlite3 db);
/**
Works like C's sqlite3_trace_v2() except that the 3rd argument to that
@@ -1078,33 +1273,33 @@ public final class SQLite3Jni {
mapping state fails and SQLITE_ERROR if the given callback object
cannot be processed propertly (i.e. an internal error).
*/
- public static synchronized native int sqlite3_trace_v2(
+ public static native int sqlite3_trace_v2(
@NotNull sqlite3 db, int traceMask, @Nullable Tracer tracer
);
- public static synchronized native UpdateHook sqlite3_update_hook(
+ public static native UpdateHook sqlite3_update_hook(
sqlite3 db, UpdateHook hook
);
- public static synchronized native byte[] sqlite3_value_blob(@NotNull sqlite3_value v);
+ public static native byte[] sqlite3_value_blob(@NotNull sqlite3_value v);
- public static synchronized native int sqlite3_value_bytes(@NotNull sqlite3_value v);
+ public static native int sqlite3_value_bytes(@NotNull sqlite3_value v);
- public static synchronized native int sqlite3_value_bytes16(@NotNull sqlite3_value v);
+ public static native int sqlite3_value_bytes16(@NotNull sqlite3_value v);
- public static synchronized native double sqlite3_value_double(@NotNull sqlite3_value v);
+ public static native double sqlite3_value_double(@NotNull sqlite3_value v);
- public static synchronized native sqlite3_value sqlite3_value_dupe(
+ public static native sqlite3_value sqlite3_value_dupe(
@NotNull sqlite3_value v
);
- public static synchronized native int sqlite3_value_encoding(@NotNull sqlite3_value v);
+ public static native int sqlite3_value_encoding(@NotNull sqlite3_value v);
- public static synchronized native void sqlite3_value_free(@Nullable sqlite3_value v);
+ public static native void sqlite3_value_free(@Nullable sqlite3_value v);
- public static synchronized native int sqlite3_value_int(@NotNull sqlite3_value v);
+ public static native int sqlite3_value_int(@NotNull sqlite3_value v);
- public static synchronized native long sqlite3_value_int64(@NotNull sqlite3_value v);
+ public static native long sqlite3_value_int64(@NotNull sqlite3_value v);
/**
If the given value was set using sqlite3_result_java_value() then
@@ -1113,7 +1308,7 @@ public final class SQLite3Jni {
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
);
@@ -1130,45 +1325,38 @@ 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 API returns null for sqlite3_value_text().
*/
- public static synchronized native String sqlite3_value_text(@NotNull sqlite3_value v);
+ 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);
+ public static String sqlite3_value_text(@NotNull sqlite3_value v){
+ final byte[] ba = sqlite3_value_text_utf8(v);
+ return null==ba ? null : new String(ba, StandardCharsets.UTF_8);
+ }
- public static synchronized native byte[] sqlite3_value_text16(@NotNull sqlite3_value v);
+ public static native byte[] sqlite3_value_text16(@NotNull sqlite3_value v);
- public static synchronized native byte[] sqlite3_value_text16le(@NotNull sqlite3_value v);
+ public static native byte[] sqlite3_value_text16le(@NotNull sqlite3_value v);
- public static synchronized native byte[] sqlite3_value_text16be(@NotNull sqlite3_value v);
+ public static native byte[] sqlite3_value_text16be(@NotNull sqlite3_value v);
- public static synchronized native int sqlite3_value_type(@NotNull sqlite3_value v);
+ public static native int sqlite3_value_type(@NotNull sqlite3_value v);
- public static synchronized native int sqlite3_value_numeric_type(@NotNull sqlite3_value v);
+ public static native int sqlite3_value_numeric_type(@NotNull sqlite3_value v);
- public static synchronized native int sqlite3_value_nochange(@NotNull sqlite3_value v);
+ public static native int sqlite3_value_nochange(@NotNull sqlite3_value v);
- public static synchronized native int sqlite3_value_frombind(@NotNull sqlite3_value v);
+ public static native int sqlite3_value_frombind(@NotNull sqlite3_value v);
- public static synchronized native int sqlite3_value_subtype(@NotNull sqlite3_value v);
-
- /**
- Cleans up all per-JNIEnv and per-db state managed by the library
- then calls the C-native sqlite3_shutdown().
- */
- public static synchronized native int sqlite3_shutdown();
+ 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.
*/
- public static synchronized native void sqlite3_do_something_for_developer();
+ public static native void sqlite3_do_something_for_developer();
//////////////////////////////////////////////////////////////////////
// SQLITE_... constants follow...
@@ -1178,11 +1366,6 @@ 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;
-
// access
public static final int SQLITE_ACCESS_EXISTS = 0;
public static final int SQLITE_ACCESS_READWRITE = 1;
diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java
index dca49faf66..cdbf860666 100644
--- a/ext/jni/src/org/sqlite/jni/Tester1.java
+++ b/ext/jni/src/org/sqlite/jni/Tester1.java
@@ -15,65 +15,116 @@ package org.sqlite.jni;
import static org.sqlite.jni.SQLite3Jni.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
-public class Tester1 {
+/**
+ 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{}
+
+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 testMethods = null;
+ //! List of exceptions collected by run()
+ private static List 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 +134,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 +179,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 +210,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 +221,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,14 +245,18 @@ 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());
rc = sqlite3_step(stmt);
+ if( SQLITE_DONE != rc ){
+ outln("step failed ??? ",rc, " ",sqlite3_errmsg(db));
+ }
affirm(SQLITE_DONE == rc);
sqlite3_finalize(stmt);
affirm(0 == stmt.getNativePointer());
@@ -236,10 +305,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 +365,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 +387,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 +412,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,7 +431,7 @@ 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);
@@ -362,7 +444,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 +473,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,9 +482,10 @@ 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 xDestroyCalled = new ValueHolder<>(false);
@@ -472,14 +555,13 @@ public class Tester1 {
rc = sqlite3_collation_needed(db, null);
affirm( 0 == rc );
sqlite3_close_v2(db);
+ affirm( 0 == db.getNativePointer() );
affirm(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 +571,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 +599,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 xDestroyCalled = new ValueHolder<>(false);
@@ -561,7 +643,49 @@ public class Tester1 {
affirm( xDestroyCalled.value );
}
- private static void testUdfJavaObject(){
+ private void testUdfThrows(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder xFuncAccum = new ValueHolder<>(0);
+
+ SQLFunction funcAgg = new SQLFunction.Aggregate(){
+ @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 SQLFunction.Scalar(){
+ @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);
+ }
+
+ private void testUdfJavaObject(){
final sqlite3 db = createNewDb();
final ValueHolder testResult = new ValueHolder<>(db);
final SQLFunction func = new SQLFunction.Scalar(){
@@ -590,7 +714,7 @@ public class Tester1 {
sqlite3_close_v2(db);
}
- private static void testUdfAggregate(){
+ private void testUdfAggregate(){
final sqlite3 db = createNewDb();
final ValueHolder xFinalNull =
// To confirm that xFinal() is called with no aggregate state
@@ -652,7 +776,7 @@ public class Tester1 {
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 */
@@ -709,14 +833,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,12 +859,12 @@ 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 counter = new ValueHolder<>(0);
/* Ensure that characters outside of the UTF BMP survive the trip
@@ -789,9 +913,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;
@@ -839,7 +965,7 @@ public class Tester1 {
}
}
- private static void testProgress(){
+ private void testProgress(){
final sqlite3 db = createNewDb();
final ValueHolder counter = new ValueHolder<>(0);
sqlite3_progress_handler(db, 1, new ProgressHandler(){
@@ -857,7 +983,7 @@ public class Tester1 {
sqlite3_close_v2(db);
}
- private static void testCommitHook(){
+ private void testCommitHook(){
final sqlite3 db = createNewDb();
final ValueHolder counter = new ValueHolder<>(0);
final ValueHolder hookResult = new ValueHolder<>(0);
@@ -906,12 +1032,13 @@ public class Tester1 {
sqlite3_close_v2(db);
}
- private static void testUpdateHook(){
+ private void testUpdateHook(){
final sqlite3 db = createNewDb();
final ValueHolder counter = new ValueHolder<>(0);
final ValueHolder expectedOp = new ValueHolder<>(0);
final UpdateHook theHook = new UpdateHook(){
@SuppressWarnings("unchecked")
+ @Override
public void xUpdateHook(int opId, String dbName, String tableName, long rowId){
++counter.value;
if( 0!=expectedOp.value ){
@@ -955,7 +1082,84 @@ 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 synchronized void testPreUpdateHook(){
+ if( !sqlite3_compileoption_used("ENABLE_PREUPDATE_HOOK") ){
+ //outln("Skipping testPreUpdateHook(): no pre-update hook support.");
+ return;
+ }
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ final ValueHolder expectedOp = new ValueHolder<>(0);
+ final PreUpdateHook theHook = new PreUpdateHook(){
+ @SuppressWarnings("unchecked")
+ @Override
+ public void xPreUpdate(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 );
+ }
+ }
+ };
+ PreUpdateHook 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 PreUpdateHook newHook = new PreUpdateHook(){
+ @Override
+ public void xPreUpdate(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 counter = new ValueHolder<>(0);
final RollbackHook theHook = new RollbackHook(){
@@ -993,9 +1197,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.");
+ @ManualTest /* 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,7 +1227,7 @@ public class Tester1 {
}
}
- private static void testAuthorizer(){
+ private void testAuthorizer(){
final sqlite3 db = createNewDb();
final ValueHolder counter = new ValueHolder<>(0);
final ValueHolder authRc = new ValueHolder<>(0);
@@ -1043,7 +1249,9 @@ public class Tester1 {
sqlite3_close(db);
}
- private static void testAutoExtension(){
+ @ManualTest/* because multiple threads legitimately make these
+ results unpredictable */
+ private synchronized void testAutoExtension(){
final ValueHolder val = new ValueHolder<>(0);
final ValueHolder toss = new ValueHolder<>(null);
final AutoExtension ax = new AutoExtension(){
@@ -1071,73 +1279,305 @@ 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.";
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 AutoExtension ax2 = new AutoExtension(){
+ public synchronized int xEntryPoint(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 /* because we 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 liArgs =
- java.util.Arrays.asList(args);
- testOpenDb1();
- testOpenDb2();
- testPrepare123();
- testBindFetchInt();
- testBindFetchInt64();
- testBindFetchDouble();
- testBindFetchText();
- testBindFetchBlob();
- testSql();
- testCollation();
- testToUtf8();
- testStatus();
- testUdf1();
- testUdfJavaObject();
- testUdfAggregate();
- testUdfWindow();
- testTrace();
- testBusy();
- testProgress();
- testCommitHook();
- testRollbackHook();
- testUpdateHook();
- testAuthorizer();
- testFts5();
- testAutoExtension();
- //testSleep();
- if(liArgs.indexOf("-v")>0){
- sqlite3_do_something_for_developer();
- //listBoundMethods();
+ private void nap() throws InterruptedException {
+ if( takeNaps ){
+ Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 17), 0);
}
- final long timeEnd = System.nanoTime();
- affirm( SQLite3Jni.uncacheJniEnv() );
- affirm( !SQLite3Jni.uncacheJniEnv() );
- outln("Tests done. Metrics:");
- outln("\tAssertions checked: "+affirmCount);
- outln("\tDatabases opened: "+metrics.dbOpen);
+ }
+ @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 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();
+ 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();
+ if( !mtMode ){
+ testAutoExtension() /* threads rightfully muck up these results */;
+ testFts5();
+ }
+ }
+ synchronized( this.getClass() ){
+ ++nTestRuns;
+ }
+ }
+
+ public void run() {
+ try {
+ runTests(0!=this.tId);
+ }catch(Exception e){
+ synchronized( listErrors ){
+ listErrors.add(e);
+ }
+ }finally{
+ affirm( SQLite3Jni.uncacheJniEnv() );
+ affirm( !SQLite3Jni.uncacheJniEnv() );
+ }
+ }
+
+ /**
+ 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 = null;
+ 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);
+ }
+ }
+ }
+
+ {
+ // Build list of tests to run from the methods named test*().
+ testMethods = new ArrayList<>();
+ 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( name.startsWith("test") ){
+ testMethods.add(m);
+ }
+ }
+ }
+ }
+
+ if( sqlLog ){
+ if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){
+ int rc = sqlite3_config( new SQLLog() {
+ @Override public void xSqllog(sqlite3 db, String msg, int op){
+ switch(op){
+ case 0: outln("Opening db: ",db); break;
+ case 1: outln(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.");
+
+ final long timeStart = System.currentTimeMillis();
+ int nLoop = 0;
+ 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." );
+ outln("libversion_number: ",
+ sqlite3_libversion_number(),"\n",
+ sqlite3_libversion(),"\n",SQLITE_SOURCE_ID);
+ 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 ){
+ if( nThread==null || nThread<=1 ){
+ new Tester1(0).runTests(false);
+ continue;
+ }
+ Tester1.mtMode = true;
+ final ExecutorService ex = Executors.newFixedThreadPool( nThread );
+ //final List> futures = new ArrayList<>();
+ ++nLoop;
+ out((1==nLoop ? "" : " ")+nLoop);
+ 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_do_something_for_developer();
+ }
+ sqlite3_shutdown();
int nMethods = 0;
int nNatives = 0;
final java.lang.reflect.Method[] declaredMethods =
@@ -1154,10 +1594,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 6439768e29..feb6d6303d 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/sqlite3_context.java b/ext/jni/src/org/sqlite/jni/sqlite3_context.java
index a61ff21c7e..d582df7838 100644
--- a/ext/jni/src/org/sqlite/jni/sqlite3_context.java
+++ b/ext/jni/src/org/sqlite/jni/sqlite3_context.java
@@ -19,8 +19,7 @@ package org.sqlite.jni;
*/
public final class sqlite3_context extends NativePointerHolder {
/**
- For use only by the JNI layer. It's permitted to set this even
- though it's private.
+ Only set by the JNI layer.
*/
private long aggregateContext = 0;
diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java
index ffdb867d9b..ef3b839bc1 100644
--- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java
+++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java
@@ -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);
}
diff --git a/manifest b/manifest
index 1df7b6f2ee..b03cdd8592 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Merge\sinto\strunk\simprovements\smade\sto\sthe\swasm\sAPIs\swhich\swere\stoo\slate\sfor\s3.43.
-D 2023-08-24T14:43:30.216
+C Merge\sthe\sjni-threading\sbranch\sinto\strunk,\seliminating\sthe\sJNI\sAPI's\sprior\sthreading\slimitations.
+D 2023-08-24T14:49:29.051
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -232,32 +232,34 @@ 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/GNUmakefile 2e17aae8debf0b0ee12010500eae7bd9557f8ad5554f0161c39a41f229e84e3e
+F ext/jni/README.md 64bf1da0d562d051207ca1c5cfa52e8b7a69120533cc034a3da7670ef920cbef
+F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
+F ext/jni/src/c/sqlite3-jni.c d7d6d420f2a13d55828cee19ba17a37c4244532dafbc5822582d7fd52ae2aaf0
+F ext/jni/src/c/sqlite3-jni.h cc24d6742b29a52338ffd3b47caf923facb8ae77f9c2fc9c2de82673bf339ea2
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/AutoExtension.java bcc1849b2fccbe5e2d7ac9e9ac7f8d05a6d7088a8fedbaad90e39569745a61e6
F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c
F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1
F ext/jni/src/org/sqlite/jni/CollationNeeded.java ad67843b6dd1c06b6b0a1dc72887b7c48e2a98042fcf6cacf14d42444037eab8
F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a
-F ext/jni/src/org/sqlite/jni/Fts5.java 13844685231e8b4840a706db3bed84d5dfcf15be0ae7e809eac40420dba24901
+F ext/jni/src/org/sqlite/jni/Fts5.java a45cd890202d72c3bfe8aea69b57b02b6dd588361af81d8b921954c37940b2f7
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 10cb2e0eb4dc5cf4241a7ccc0442a680f14a3ce6ecbb726552f2b5e026e521e0
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/NativePointerHolder.java 8110d4cfb20884e8ed241de7420c615b040a9f9c441d9cff06f34833399244a8
+F ext/jni/src/org/sqlite/jni/OutputPointer.java bb09fee5ad51d10e58075de000f8c1a3622a6c4b6a390ef134b6add1bfb32ca1
+F ext/jni/src/org/sqlite/jni/PreUpdateHook.java dec00a706b58c67989f0ff56c4f0a703821d25b45c62dd7fed1b462049f15c26
F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc
F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7ce7797f2c6c7fca2004ff12ce20f86
F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564
-F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46
-F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 4b6fd22e04e63eb65d8e4e38fda39ecf15ce244d034607517627ce2e766e7e65
-F ext/jni/src/org/sqlite/jni/Tester1.java 4253dc7bcff64500a9388f1a17d3d39dbe4eb9d7db9fc035ce6e2380d45ad5fc
-F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1
+F ext/jni/src/org/sqlite/jni/SQLFunction.java f697cf2a81c4119f2baf0682af689686f0466f1dd83dba00885f5603e693fe16
+F ext/jni/src/org/sqlite/jni/SQLLog.java c60610b35208416940822e834d61f08fbbe5d6e06b374b541b49e41fd56c9798
+F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 956063c854c4f662183c41c65d0ab48b5e2127824b8053eeb05b9fc40f0d09e3
+F ext/jni/src/org/sqlite/jni/Tester1.java 9b6ec0ae299a56822e82e7dc2cf7ef1031ae87bcb595065bef84b7edac7114f5
+F ext/jni/src/org/sqlite/jni/TesterFts5.java 6f135c60e24c89e8eecb9fe61dde0f3bb2906de668ca6c9186bcf34bdaf94629
F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d
F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d
F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee
@@ -265,10 +267,10 @@ F ext/jni/src/org/sqlite/jni/fts5_api.java 5198be71c162e3e0cb1f4962a7cdf0d7596e8
F ext/jni/src/org/sqlite/jni/fts5_extension_function.java ac825035d7d83fc7fd960347abfa6803e1614334a21533302041823ad5fc894c
F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b
F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c388ec3bc1de547f098a0217158fc
-F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810
+F ext/jni/src/org/sqlite/jni/sqlite3_context.java fe7797a696978f057528a57b7a11e7797ed41fd7afcf100c5ebb67055d9f706f
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 2835eb3dd1e14767ca49354c224150c70300d8013d6d51dd875f7d9380faa278
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
@@ -2092,9 +2094,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 0f80b798b3f4b81a7bb4233c58294edd0f1156f36b6ecf5ab8e83631d468778c b8f6a50a4bf9478324f0272d79f2fe6992a49b753e79e39a268c6afb261bb01e
-R 7ae5b52e7df2292254903c2a58e5680f
-T +closed b8f6a50a4bf9478324f0272d79f2fe6992a49b753e79e39a268c6afb261bb01e Closed\sby\sintegrate-merge.
+P ac9da5c79a4f56d25202d50974e16e2a463d77c99b1907aee2605d5a3e50a565 1f46ba8d3bc61af771c1e33d09ad25f0da4fc4f915f7a9f6223ebfd99526d81d
+R 77949ce12a43210a6d0344be95da4143
+T +closed 1f46ba8d3bc61af771c1e33d09ad25f0da4fc4f915f7a9f6223ebfd99526d81d Closed\sby\sintegrate-merge.
U stephan
-Z 43037d70eaecfc3116c1946ba3ee4fe8
+Z 8355fada3860b53acd7e17e529df543a
# Remove this line to create a well-formed Fossil manifest.
diff --git a/manifest.uuid b/manifest.uuid
index 11b51edd1d..220fc74d64 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-ac9da5c79a4f56d25202d50974e16e2a463d77c99b1907aee2605d5a3e50a565
\ No newline at end of file
+3739c8aa7080d8e1044ca51ab7b699b50da4d29620b35acfcea2745059b65bac
\ No newline at end of file