mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-30 19:03:16 +03:00
Merge the jni-threading branch into trunk, eliminating the JNI API's prior threading limitations.
FossilOrigin-Name: 3739c8aa7080d8e1044ca51ab7b699b50da4d29620b35acfcea2745059b65bac
This commit is contained in:
@ -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)/.
|
||||
|
@ -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.
|
||||
|
||||
|
||||
<a id='1to1ish'></a>
|
||||
One-to-One(-ish) Mapping to C
|
||||
========================================================================
|
||||
|
||||
This JNI binding aims to provide as close to a 1-to-1 experience with
|
||||
the C API as cross-language semantics allow. Exceptions are
|
||||
the C API as cross-language semantics allow. Interface changes are
|
||||
necessarily made where cross-language semantics do not allow a 1-to-1,
|
||||
and judiciously made where a 1-to-1 mapping would be unduly cumbersome
|
||||
to use in Java.
|
||||
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.
|
||||
|
@ -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)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -23,7 +23,7 @@ import java.nio.charset.StandardCharsets;
|
||||
public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi> {
|
||||
//! Only called from JNI
|
||||
private Fts5ExtensionApi(){}
|
||||
private int iVersion = 2;
|
||||
private final int iVersion = 2;
|
||||
|
||||
/* Callback type for used by xQueryPhrase(). */
|
||||
public static interface xQueryPhraseCallback {
|
||||
@ -33,54 +33,54 @@ public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi
|
||||
/**
|
||||
Returns the singleton instance of this class.
|
||||
*/
|
||||
public static synchronized native Fts5ExtensionApi getInstance();
|
||||
public static native Fts5ExtensionApi getInstance();
|
||||
|
||||
public synchronized native int xColumnCount(@NotNull Fts5Context fcx);
|
||||
public synchronized native int xColumnSize(@NotNull Fts5Context cx, int iCol,
|
||||
public native int xColumnCount(@NotNull Fts5Context fcx);
|
||||
public native int xColumnSize(@NotNull Fts5Context cx, int iCol,
|
||||
@NotNull OutputPointer.Int32 pnToken);
|
||||
public synchronized native int xColumnText(@NotNull Fts5Context cx, int iCol,
|
||||
public native int xColumnText(@NotNull Fts5Context cx, int iCol,
|
||||
@NotNull OutputPointer.String txt);
|
||||
public synchronized native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol,
|
||||
public native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol,
|
||||
@NotNull OutputPointer.Int64 pnToken);
|
||||
public synchronized native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt);
|
||||
public synchronized native int xInst(@NotNull Fts5Context cx, int iIdx,
|
||||
public native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt);
|
||||
public native int xInst(@NotNull Fts5Context cx, int iIdx,
|
||||
@NotNull OutputPointer.Int32 piPhrase,
|
||||
@NotNull OutputPointer.Int32 piCol,
|
||||
@NotNull OutputPointer.Int32 piOff);
|
||||
public synchronized native int xInstCount(@NotNull Fts5Context fcx,
|
||||
public native int xInstCount(@NotNull Fts5Context fcx,
|
||||
@NotNull OutputPointer.Int32 pnInst);
|
||||
public synchronized native int xPhraseCount(@NotNull Fts5Context fcx);
|
||||
public synchronized native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase,
|
||||
public native int xPhraseCount(@NotNull Fts5Context fcx);
|
||||
public native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase,
|
||||
@NotNull Fts5PhraseIter iter,
|
||||
@NotNull OutputPointer.Int32 iCol,
|
||||
@NotNull OutputPointer.Int32 iOff);
|
||||
public synchronized native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase,
|
||||
public native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase,
|
||||
@NotNull Fts5PhraseIter iter,
|
||||
@NotNull OutputPointer.Int32 iCol);
|
||||
public synchronized native void xPhraseNext(@NotNull Fts5Context cx,
|
||||
public native void xPhraseNext(@NotNull Fts5Context cx,
|
||||
@NotNull Fts5PhraseIter iter,
|
||||
@NotNull OutputPointer.Int32 iCol,
|
||||
@NotNull OutputPointer.Int32 iOff);
|
||||
public synchronized native void xPhraseNextColumn(@NotNull Fts5Context cx,
|
||||
public native void xPhraseNextColumn(@NotNull Fts5Context cx,
|
||||
@NotNull Fts5PhraseIter iter,
|
||||
@NotNull OutputPointer.Int32 iCol);
|
||||
public synchronized native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase);
|
||||
public synchronized native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase,
|
||||
public native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase);
|
||||
public native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase,
|
||||
@NotNull xQueryPhraseCallback callback);
|
||||
public synchronized native int xRowCount(@NotNull Fts5Context fcx,
|
||||
public native int xRowCount(@NotNull Fts5Context fcx,
|
||||
@NotNull OutputPointer.Int64 nRow);
|
||||
public synchronized native long xRowid(@NotNull Fts5Context cx);
|
||||
public native long xRowid(@NotNull Fts5Context cx);
|
||||
/* Note that the JNI binding lacks the C version's xDelete()
|
||||
callback argument. Instead, if pAux has an xDestroy() method, it
|
||||
is called if the FTS5 API finalizes the aux state (including if
|
||||
allocation of storage for the auxdata fails). Any reference to
|
||||
pAux held by the JNI layer will be relinquished regardless of
|
||||
whether pAux has an xDestroy() method. */
|
||||
public synchronized native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux);
|
||||
public synchronized native int xTokenize(@NotNull Fts5Context cx, @NotNull byte pText[],
|
||||
public native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux);
|
||||
public native int xTokenize(@NotNull Fts5Context cx, @NotNull byte[] pText,
|
||||
@NotNull Fts5.xTokenizeCallback callback);
|
||||
|
||||
public synchronized native Object xUserData(Fts5Context cx);
|
||||
public native Object xUserData(Fts5Context cx);
|
||||
//^^^ returns the pointer passed as the 3rd arg to the C-level
|
||||
// fts5_api::xCreateFunction.
|
||||
// fts5_api::xCreateFunction().
|
||||
}
|
||||
|
@ -28,6 +28,6 @@ package org.sqlite.jni;
|
||||
*/
|
||||
public class NativePointerHolder<ContextType> {
|
||||
//! Only set from JNI, where access permissions don't matter.
|
||||
private long nativePointer = 0;
|
||||
private volatile long nativePointer = 0;
|
||||
public final long getNativePointer(){ return nativePointer; }
|
||||
}
|
||||
|
@ -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.
|
||||
|
29
ext/jni/src/org/sqlite/jni/PreUpdateHook.java
Normal file
29
ext/jni/src/org/sqlite/jni/PreUpdateHook.java
Normal file
@ -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 );
|
||||
}
|
@ -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<T>, and Window<T>. Most simply, clients may create
|
||||
anonymous classes from those to implement UDFs. Clients are free to
|
||||
create their own classes for use with UDFs, so long as they conform
|
||||
to the public interfaces defined by those three classes. The JNI
|
||||
layer only actively relies on the SQLFunction base class.
|
||||
Aggregate<T>, and Window<T>. Most simply, clients may subclass
|
||||
those, or create anonymous classes from them, to implement
|
||||
UDFs. Clients are free to create their own classes for use with
|
||||
UDFs, so long as they conform to the public interfaces defined by
|
||||
those three classes. The JNI layer only actively relies on the
|
||||
SQLFunction base class and the method names and signatures used by
|
||||
the UDF callback interfaces.
|
||||
*/
|
||||
public abstract class SQLFunction {
|
||||
|
||||
/**
|
||||
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<T> 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<T> extends Aggregate<T> {
|
||||
|
||||
//! 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);
|
||||
}
|
||||
}
|
||||
|
25
ext/jni/src/org/sqlite/jni/SQLLog.java
Normal file
25
ext/jni/src/org/sqlite/jni/SQLLog.java
Normal file
@ -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 );
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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<java.lang.reflect.Method> testMethods = null;
|
||||
//! List of exceptions collected by run()
|
||||
private static List<Exception> listErrors = new ArrayList<>();
|
||||
private static final class Metrics {
|
||||
int dbOpen;
|
||||
//! Number of times createNewDb() (or equivalent) is invoked.
|
||||
volatile int dbOpen = 0;
|
||||
}
|
||||
|
||||
private Integer tId;
|
||||
|
||||
Tester1(Integer id){
|
||||
tId = id;
|
||||
}
|
||||
|
||||
static final Metrics metrics = new Metrics();
|
||||
private static final OutputPointer.sqlite3_stmt outStmt
|
||||
= new OutputPointer.sqlite3_stmt();
|
||||
|
||||
public static void out(Object val){
|
||||
System.out.print(val);
|
||||
public synchronized static void outln(){
|
||||
if( !quietMode ){
|
||||
System.out.println("");
|
||||
}
|
||||
}
|
||||
|
||||
public static void outln(Object val){
|
||||
System.out.println(val);
|
||||
public synchronized static void outln(Object val){
|
||||
if( !quietMode ){
|
||||
System.out.print(Thread.currentThread().getName()+": ");
|
||||
System.out.println(val);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized static void out(Object val){
|
||||
if( !quietMode ){
|
||||
System.out.print(val);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void out(Object... vals){
|
||||
int n = 0;
|
||||
for(Object v : vals) out((n++>0 ? " " : "")+v);
|
||||
public synchronized static void out(Object... vals){
|
||||
if( !quietMode ){
|
||||
System.out.print(Thread.currentThread().getName()+": ");
|
||||
for(Object v : vals) out(v);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void outln(Object... vals){
|
||||
out(vals); out("\n");
|
||||
public synchronized static void outln(Object... vals){
|
||||
if( !quietMode ){
|
||||
out(vals); out("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static int affirmCount = 0;
|
||||
public static void affirm(Boolean v){
|
||||
static volatile int affirmCount = 0;
|
||||
public synchronized static void affirm(Boolean v, String comment){
|
||||
++affirmCount;
|
||||
assert( v /* prefer assert over exception if it's enabled because
|
||||
the JNI layer sometimes has to suppress exceptions. */);
|
||||
if( !v ) throw new RuntimeException("Assertion failed.");
|
||||
if( false ) assert( v /* prefer assert over exception if it's enabled because
|
||||
the JNI layer sometimes has to suppress exceptions,
|
||||
so they might be squelched on their way back to the
|
||||
top. */);
|
||||
if( !v ) throw new RuntimeException(comment);
|
||||
}
|
||||
|
||||
private static void test1(){
|
||||
outln("libversion_number:",
|
||||
sqlite3_libversion_number()
|
||||
+ "\n"
|
||||
+ sqlite3_libversion()
|
||||
+ "\n"
|
||||
+ SQLITE_SOURCE_ID);
|
||||
public static void affirm(Boolean v){
|
||||
affirm(v, "Affirmation failed.");
|
||||
}
|
||||
|
||||
@ManualTest /* because testing this for threading is pointless */
|
||||
private void test1(){
|
||||
affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
|
||||
//outln("threadsafe = "+sqlite3_threadsafe());
|
||||
affirm(SQLITE_MAX_LENGTH > 0);
|
||||
affirm(SQLITE_MAX_TRIGGER_DEPTH>0);
|
||||
}
|
||||
|
||||
public static sqlite3 createNewDb(){
|
||||
static sqlite3 createNewDb(){
|
||||
final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
|
||||
int rc = sqlite3_open(":memory:", out);
|
||||
++metrics.dbOpen;
|
||||
sqlite3 db = out.take();
|
||||
if( 0!=rc ){
|
||||
final String msg = db.getNativePointer()==0
|
||||
? sqlite3_errstr(rc)
|
||||
: sqlite3_errmsg(db);
|
||||
final String msg =
|
||||
null==db ? sqlite3_errstr(rc) : sqlite3_errmsg(db);
|
||||
sqlite3_close(db);
|
||||
throw new RuntimeException("Opening db failed: "+msg);
|
||||
}
|
||||
affirm( null == out.get() );
|
||||
@ -83,17 +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<Boolean> 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<Boolean> 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<Integer> xFuncAccum = new ValueHolder<>(0);
|
||||
|
||||
SQLFunction funcAgg = new SQLFunction.Aggregate<Integer>(){
|
||||
@Override public void xStep(sqlite3_context cx, sqlite3_value[] args){
|
||||
/** Throwing from here should emit loud noise on stdout or stderr
|
||||
but the exception is supressed because we have no way to inform
|
||||
sqlite about it from these callbacks. */
|
||||
//throw new RuntimeException("Throwing from an xStep");
|
||||
}
|
||||
@Override public void xFinal(sqlite3_context cx){
|
||||
throw new RuntimeException("Throwing from an xFinal");
|
||||
}
|
||||
};
|
||||
int rc = sqlite3_create_function(db, "myagg", 1, SQLITE_UTF8, funcAgg);
|
||||
affirm(0 == rc);
|
||||
affirm(0 == xFuncAccum.value);
|
||||
sqlite3_stmt stmt = prepare(db, "SELECT myagg(1)");
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
affirm( 0 != rc );
|
||||
affirm( sqlite3_errmsg(db).indexOf("an xFinal") > 0 );
|
||||
|
||||
SQLFunction funcSc = new 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<sqlite3> 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<Boolean> 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<Integer> 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<Integer> 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<Integer> counter = new ValueHolder<>(0);
|
||||
final ValueHolder<Integer> 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<Integer> counter = new ValueHolder<>(0);
|
||||
final ValueHolder<Integer> 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<Integer> counter = new ValueHolder<>(0);
|
||||
final ValueHolder<Integer> 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<Integer> 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<Integer> counter = new ValueHolder<>(0);
|
||||
final ValueHolder<Integer> 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<Integer> val = new ValueHolder<>(0);
|
||||
final ValueHolder<String> 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<String> 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<java.lang.reflect.Method> mlist = testMethods;
|
||||
affirm( null!=mlist );
|
||||
if( shuffle ){
|
||||
mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) );
|
||||
java.util.Collections.shuffle(mlist);
|
||||
}
|
||||
if( listRunTests ){
|
||||
synchronized(this.getClass()){
|
||||
if( !fromThread ){
|
||||
out("Initial test"," list: ");
|
||||
for(java.lang.reflect.Method m : testMethods){
|
||||
out(m.getName()+" ");
|
||||
}
|
||||
outln();
|
||||
outln("(That list excludes some which are hard-coded to run.)");
|
||||
}
|
||||
out("Running"," tests: ");
|
||||
for(java.lang.reflect.Method m : mlist){
|
||||
out(m.getName()+" ");
|
||||
}
|
||||
outln();
|
||||
}
|
||||
}
|
||||
testToUtf8();
|
||||
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<Future<?>> 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");
|
||||
}
|
||||
}
|
||||
|
@ -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); }
|
||||
}
|
||||
|
@ -19,8 +19,7 @@ package org.sqlite.jni;
|
||||
*/
|
||||
public final class sqlite3_context extends NativePointerHolder<sqlite3_context> {
|
||||
/**
|
||||
For use only by the JNI layer. It's permitted to set this even
|
||||
though it's private.
|
||||
Only set by the JNI layer.
|
||||
*/
|
||||
private long aggregateContext = 0;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user