1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-08-01 06:27:03 +03:00

Add more JNI docs, tests, and a handful of Java-side overloads.

FossilOrigin-Name: d19a431facbde6a6b960664674753ee85d2c051a76109ce7db0b079c65fbdea0
This commit is contained in:
stephan
2023-08-24 11:57:51 +00:00
parent 8cafdfa916
commit bfdc7ab5a7
11 changed files with 395 additions and 138 deletions

View File

@ -223,19 +223,18 @@ $(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE)
$(sqlite3-jni.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h)
$(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE)
$(CC) $(sqlite3-jni.dll.cflags) $(SQLITE_OPT) \
$(sqlite3-jni.c) -shared -o $@ -lpthread
$(sqlite3-jni.c) -shared -o $@
all: $(sqlite3-jni.dll)
.PHONY: test
test.flags ?= -v
test.flags ?=
test.main.flags = -ea -Djava.library.path=$(dir.bld.c) \
$(java.flags) -cp $(classpath) \
org.sqlite.jni.Tester1
test: $(SQLite3Jni.class) $(sqlite3-jni.dll)
$(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
$(java.flags) -cp $(classpath) \
org.sqlite.jni.Tester1 $(test.flags)
test-mt: $(SQLite3Jni.class) $(sqlite3-jni.dll)
$(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
$(java.flags) -cp $(classpath) \
org.sqlite.jni.Tester1 -t 7 -r 50 -shuffle $(test.flags)
$(bin.java) $(test.main.flags) $(test.flags)
@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
@ -271,7 +270,7 @@ endif
tester-ext: tester-local
tester: tester-ext
tests: test test-mt tester
tests: test tester
package.jar.in := $(abspath $(dir.src)/jar.in)
CLEAN_FILES += $(package.jar.in)
$(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main)

View File

@ -16,7 +16,7 @@ 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. The JNI
bindgins released with version 3.43 are a "tech preview" and 3.44
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.
@ -43,29 +43,34 @@ Non-goals:
- Creation of high-level OO wrapper APIs. Clients are free to create
them off of the C-style API.
Hello World
-----------------------------------------------------------------------
```java
import org.sqlite.jni.*;
import static org.sqlite.jni.SQLite3Jni;
import static SQLite3Jni.*;
...
OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
int rc = sqlite3_open(":memory:", out);
final sqlite3 db = out.take();
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));
sqlite3_close(db);
}else{
System.out.print("Error opening db: rc="+rc);
}
... handle error ...
}
... use db ...
// ... 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
@ -86,28 +91,90 @@ $ make test
$ make clean
```
The jar distribution can be created with `make jar`.
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. 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.
the C API as cross-language semantics allow. Interface changes are
necessarily made where cross-language semantics do not allow a 1-to-1,
and judiciously made where a 1-to-1 mapping would be unduly cumbersome
to use in Java. In all cases, this binding makes every effort to
provide semantics compatible with the C API documentation even if the
interface to those semantics is slightly different. Any cases which
deviate from those semantics (either removing or adding semantics) are
clearly documented.
Golden Rule: _Never_ Throw from Callbacks (Unless...)
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
------------------------------------------------------------------------
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.
Golden Rule #2: _Never_ Throw from Callbacks (Unless...)
------------------------------------------------------------------------
All routines in this API, barring explicitly documented exceptions,
retain C-like semantics. For example, they are not permitted to throw
or propagate exceptions and must return error information (if any) via
result codes or `null`. The only cases where the C-style APIs may
throw is through client-side misuse, e.g. passing in a null where it
shouldn't be used. The APIs clearly mark function parameters which
should not be null, but does not actively defend itself against such
misuse. Some C-style APIs explicitly accept `null` as a no-op for
usability's sake, and some of the JNI APIs deliberately return an
error code, instead of segfaulting, when passed a `null`.
Client-defined callbacks _must never throw exceptions_ unless _very
explicitly documented_ as being throw-safe. Exceptions are generally
reserved for higher-level bindings which are constructed to
specifically deal with them and ensure that they do not leak C-level
resources. In some cases, callback handlers (see below) are permitted
to throw, in which cases they get translated to C-level result codes
and/or messages.
resources. In some cases, callback handlers are permitted to throw, in
which cases they get translated to C-level result codes and/or
messages. If a callback which is not permitted to throw throws, its
exception may trigger debug output but will otherwise be suppressed.
The reason some callbacks are permitted to throw and others not is
because all such callbacks act as proxies for C function callback
interfaces and some of those interfaces have no error-reporting
mechanism. Those which are capable of propagating errors back through
the library convert exceptions from callbacks into corresponding
C-level error information. Those which cannot propagate errors
necessarily suppress any exceptions in order to maintain the C-style
semantics of the APIs.
Awkward Callback Names
@ -246,4 +313,5 @@ in-flux nature of this API.
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.

View File

@ -46,11 +46,13 @@ $(sqlite3-jni.dll):
echo "*** to configure it for your system. ***"; \
echo "************************************************************************"
$(CC) $(CFLAGS) $(SQLITE_OPT) \
src/sqlite3-jni.c -lpthread -shared -o $@
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)

View File

@ -1429,7 +1429,7 @@ typedef struct {
} ResultJavaVal;
/* For use with sqlite3_result/value_pointer() */
#define ResultJavaValuePtrStr "ResultJavaVal"
#define ResultJavaValuePtrStr "org.sqlite.jni.ResultJavaVal"
/*
** Allocate a new ResultJavaVal and assign it a new global ref of
@ -1915,15 +1915,10 @@ JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){
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);
jbyte * const pBuf = baData ? JBA_TOC(baData) : 0;
int const rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx,
pBuf, (int)nMax, SQLITE_TRANSIENT);
JBA_RELEASE(baData,pBuf);
}
return (jint)rc;
}
@ -1960,15 +1955,21 @@ JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName
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,
jbyte * const pBuf = baData ? JBA_TOC(baData) : 0;
int const 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);
}
JDECL(jint,1bind_1text16)(JENV_CSELF, jobject jpStmt,
jint ndx, jbyteArray baData, jint nMax){
jbyte * const pBuf = baData ? JBA_TOC(baData) : 0;
int const rc = sqlite3_bind_text16(PtrGet_sqlite3_stmt(jpStmt), (int)ndx,
pBuf, (int)nMax, SQLITE_TRANSIENT);
JBA_RELEASE(baData, pBuf);
return (jint)rc;
}
JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt,
@ -3595,18 +3596,19 @@ JDECL(jbyteArray,1value_1text_1utf8)(JENV_CSELF, jobject jpSVal){
}
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");

View File

@ -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

View File

@ -14,24 +14,24 @@
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 a sqlite3_auto_extension()
callback, with the caveat that the signature is more limited.
callback, with the caveat that the signature is shorter.
As an exception (as it were) to the callbacks-must-not-throw
rule, AutoExtensions may throw and the exception's error message
AutoExtensions may throw and the exception's error message
will be set as the db's error string.
Hints for implementations:
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 is a good idea to make the
- 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.

View File

@ -154,20 +154,27 @@ 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 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 (it depends
on multiple factors).
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.
*/
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
){
@ -176,10 +183,6 @@ public final class SQLite3Jni {
: sqlite3_bind_blob(stmt, ndx, data, data.length);
}
private static native int sqlite3_bind_blob(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n
);
public static native int sqlite3_bind_double(
@NotNull sqlite3_stmt stmt, int ndx, double v
);
@ -200,13 +203,10 @@ public final class SQLite3Jni {
@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.
Requires that paramName be a NUL-terminated UTF-8 string.
*/
private static native int sqlite3_bind_parameter_index(
public static native int sqlite3_bind_parameter_index(
@NotNull sqlite3_stmt stmt, byte[] paramName
);
@ -218,14 +218,22 @@ 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.
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
){
@ -234,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
){
@ -242,6 +253,41 @@ public final class SQLite3Jni {
: sqlite3_bind_text(stmt, ndx, data, data.length);
}
/**
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 native int sqlite3_bind_text16(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes
);
/**
Converts its string argument to UTF-16 and binds it as such, returning
the result of the C-side function of the same name. The 3rd argument
may be null.
*/
public static int sqlite3_bind_text16(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable String data
){
if(null == data) return sqlite3_bind_null(stmt, ndx);
final byte[] bytes = data.getBytes(StandardCharsets.UTF_16);
return sqlite3_bind_text16(stmt, 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
);
@ -253,8 +299,7 @@ public final class SQLite3Jni {
/**
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 native int sqlite3_busy_handler(
@NotNull sqlite3 db, @Nullable BusyHandler handler
@ -595,15 +640,41 @@ public final class SQLite3Jni {
@Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb
);
/**
Convenience overload which returns its db handle directly. The returned
object might not have been successfully opened: use sqlite3_errcode() to
check whether it is in an error state.
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.
@ -648,6 +719,26 @@ public final class SQLite3Jni {
return sqlite3_prepare(db, utf8, utf8.length, outStmt, null);
}
/**
Convenience overload which returns its statement handle directly,
or null on error or when reading only whitespace or
comments. sqlite3_errcode() can be used to determine whether
there was an error or the input was empty. Ownership of the
returned object is passed to the caller, who must eventually pass
it to sqlite3_finalize().
*/
public static sqlite3_stmt sqlite3_prepare(
@NotNull sqlite3 db, @NotNull String sql
){
final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
sqlite3_prepare(db, sql, out);
return out.take();
}
/**
See sqlite3_prepare() 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,
@ -677,6 +768,18 @@ public final class SQLite3Jni {
return sqlite3_prepare_v2(db, utf8, utf8.length, outStmt, null);
}
/**
Works identically to the sqlite3_stmt-returning sqlite3_prepare()
but uses sqlite3_prepare_v2().
*/
public static sqlite3_stmt sqlite3_prepare_v2(
@NotNull sqlite3 db, @NotNull String sql
){
final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
sqlite3_prepare_v2(db, sql, out);
return out.take();
}
private static native int sqlite3_prepare_v3(
@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes,
int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt,
@ -706,6 +809,18 @@ public final class SQLite3Jni {
return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null);
}
/**
Works identically to the sqlite3_stmt-returning sqlite3_prepare()
but uses sqlite3_prepare_v3().
*/
public static sqlite3_stmt sqlite3_prepare_v3(
@NotNull sqlite3 db, @NotNull String sql, int prepFlags
){
final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
sqlite3_prepare_v3(db, sql, prepFlags, out);
return out.take();
}
/**
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

View File

@ -21,6 +21,16 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
An annotation for Tester1 tests which we do not want to run in
reflection-driven test mode because either they are not suitable
for multi-threaded threaded mode or we have to control their execution
order.
*/
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
@interface ManualTest{}
public class Tester1 implements Runnable {
//! True when running in multi-threaded mode.
private static boolean mtMode = false;
@ -30,6 +40,10 @@ public class Tester1 implements Runnable {
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()
@ -48,28 +62,38 @@ public class Tester1 implements Runnable {
static final Metrics metrics = new Metrics();
public synchronized static void outln(){
if( !quietMode ){
System.out.println("");
}
}
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 synchronized static void out(Object... vals){
if( !quietMode ){
System.out.print(Thread.currentThread().getName()+": ");
for(Object v : vals) out(v);
}
}
@SuppressWarnings("unchecked")
public synchronized static void outln(Object... vals){
if( !quietMode ){
out(vals); out("\n");
}
}
static volatile int affirmCount = 0;
public synchronized static void affirm(Boolean v, String comment){
@ -85,6 +109,7 @@ public class Tester1 implements Runnable {
affirm(v, "Affirmation failed.");
}
@ManualTest /* because testing this for threading is pointless */
private void test1(){
affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
affirm(SQLITE_MAX_LENGTH > 0);
@ -280,6 +305,16 @@ public class Tester1 implements Runnable {
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);
}
@ -383,8 +418,11 @@ public class Tester1 implements Runnable {
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);
@ -393,7 +431,7 @@ public class Tester1 implements Runnable {
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);
@ -521,6 +559,7 @@ public class Tester1 implements Runnable {
affirm(xDestroyCalled.value);
}
@ManualTest /* because threading is meaningless here */
private void testToUtf8(){
/**
https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
@ -873,6 +912,7 @@ public class Tester1 implements Runnable {
affirm( 7 == counter.value );
}
@ManualTest /* because threads inherently break this test */
private void testBusy(){
final String dbName = "_busy-handler.db";
final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3();
@ -1156,6 +1196,8 @@ public class Tester1 implements Runnable {
it throws.
*/
@SuppressWarnings("unchecked")
@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.");
@ -1206,6 +1248,8 @@ public class Tester1 implements Runnable {
sqlite3_close(db);
}
@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);
@ -1296,6 +1340,7 @@ public class Tester1 implements Runnable {
affirm( 8 == val.value );
}
@ManualTest /* because we only want to run this test manually */
private void testSleep(){
out("Sleeping briefly... ");
sqlite3_sleep(600);
@ -1308,6 +1353,7 @@ public class Tester1 implements Runnable {
}
}
@ManualTest /* because we only want to run this test on demand */
private void testFail(){
affirm( false, "Intentional failure." );
}
@ -1355,6 +1401,9 @@ public class Tester1 implements Runnable {
testFts5();
}
}
synchronized( this.getClass() ){
++nTestRuns;
}
}
public void run() {
@ -1375,6 +1424,8 @@ public class Tester1 implements Runnable {
CLI flags:
-q|-quiet: disables most test output.
-t|-thread N: runs the tests in N threads
concurrently. Default=1.
@ -1400,6 +1451,7 @@ public class Tester1 implements Runnable {
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("-")){
@ -1421,6 +1473,8 @@ public class Tester1 implements Runnable {
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);
}
@ -1430,20 +1484,17 @@ public class Tester1 implements Runnable {
{
// Build list of tests to run from the methods named test*().
testMethods = new ArrayList<>();
final List<String> excludes = new ArrayList<>();
// Tests we want to control the order of:
if( !forceFail ) excludes.add("testFail");
excludes.add("test1");
excludes.add("testAutoExtension");
excludes.add("testBusy");
excludes.add("testFts5");
excludes.add("testSleep");
excludes.add("testToUtf8");
for(java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){
for(final java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){
final String name = m.getName();
if( name.startsWith("test") && excludes.indexOf(name)<0 ){
if( name.equals("testFail") ){
if( forceFail ){
testMethods.add(m);
}
}else if( !m.isAnnotationPresent( ManualTest.class ) ){
if( name.startsWith("test") ){
testMethods.add(m);
}
}
}
}
@ -1465,6 +1516,11 @@ public class Tester1 implements Runnable {
}
}
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 ),
@ -1476,7 +1532,7 @@ public class Tester1 implements Runnable {
outln("libversion_number: ",
sqlite3_libversion_number(),"\n",
sqlite3_libversion(),"\n",SQLITE_SOURCE_ID);
outln("Running ",nRepeat," loop(s) over ",nThread," thread(s).");
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 ){
@ -1500,6 +1556,7 @@ public class Tester1 implements Runnable {
Thread.currentThread().interrupt();
}
if( !listErrors.isEmpty() ){
quietMode = false;
outln("TEST ERRORS:");
Exception err = null;
for( Exception e : listErrors ){
@ -1510,9 +1567,10 @@ public class Tester1 implements Runnable {
}
}
outln();
quietMode = false;
final long timeEnd = System.currentTimeMillis();
outln("Tests done. Metrics:");
outln("Tests done. Metrics across ",nTestRuns," total iteration(s):");
outln("\tAssertions checked: ",affirmCount);
outln("\tDatabases opened: ",metrics.dbOpen);
if( doSomethingForDev ){

View File

@ -72,7 +72,8 @@ public class TesterFts5 {
affirm( xDestroyCalled.value );
}
public TesterFts5(){
public TesterFts5(boolean verbose){
if(verbose){
final long timeStart = System.currentTimeMillis();
final int oldAffirmCount = Tester1.affirmCount;
test1();
@ -80,5 +81,9 @@ public class TesterFts5 {
final long timeEnd = System.currentTimeMillis();
outln("FTS5 Tests done. Assertions checked = ",affirmCount,
", Total time = ",(timeEnd - timeStart),"ms");
}else{
test1();
}
}
public TesterFts5(){ this(false); }
}

View File

@ -1,5 +1,5 @@
C JNI\scleanups\sregarding\sbuilding\swith\scertain\sfeatures\sdisabled.
D 2023-08-23T17:52:51.175
C Add\smore\sJNI\sdocs,\stests,\sand\sa\shandful\sof\sJava-side\soverloads.
D 2023-08-24T11:57:51.863
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -232,13 +232,13 @@ 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 14b7c3abd1ae8693203b08b0e06bb359f8924ad2243f15953e9c6e456ae317b5
F ext/jni/README.md 1693e865d366f5ebaa756732ea0d4b786515caf3cfbcd4dcb8758274373913b0
F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3
F ext/jni/src/c/sqlite3-jni.c 0ca96134d7fb3f313a7a49487f68a8d7a6d7545470c84532aa1ce63d2cdc432e
F ext/jni/src/c/sqlite3-jni.h c5cb0348efe4e5f3d125a240e2437e8475de14a586c2f859e2acdcde4116244d
F ext/jni/GNUmakefile 0a823c56f081294e7797dae303380ac989ebaa801bba970968342b7358f07aed
F ext/jni/README.md 64bf1da0d562d051207ca1c5cfa52e8b7a69120533cc034a3da7670ef920cbef
F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
F ext/jni/src/c/sqlite3-jni.c e1e3cde4d08925282b5bc949f9ed8f613a6a2c6f60d0c697e79d59fb49f9fe4b
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 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd
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
@ -257,9 +257,9 @@ F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7c
F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564
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 f64554457fa30a048ef99374bfac3c4b986e3528353dce3086a98a858e3fe000
F ext/jni/src/org/sqlite/jni/Tester1.java 69ea63a5b235f94f914dff6fe3ecd103ee0a8023b8737db071b46c0c75375e26
F ext/jni/src/org/sqlite/jni/TesterFts5.java de095e3b701fba0c56d7b8b2993dc22bcbaa9de8f992904a93729ad729a91576
F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 956063c854c4f662183c41c65d0ab48b5e2127824b8053eeb05b9fc40f0d09e3
F ext/jni/src/org/sqlite/jni/Tester1.java b5a4bb2a969df053d5c138887f04039a79b36170372a2efdf5dfbd6ac90db4c9
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
@ -2094,8 +2094,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P d67255f7251cc5d1d27d77d4c84ff216e2da71202db989718189a6b4beff1cd0
R b911867dcaf18c3e131e156c82d306fe
P a9e6d5158b8a4a6b8554a5f8f0a35785ee450d42ea877275dc27085e89716c18
R cb79df5ad40cc86377d98f5a1329b589
U stephan
Z bdef86836c9254e732e1b6d744febf17
Z 39e8fbdcbc2ebaa58ed84b96f0374c06
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
a9e6d5158b8a4a6b8554a5f8f0a35785ee450d42ea877275dc27085e89716c18
d19a431facbde6a6b960664674753ee85d2c051a76109ce7db0b079c65fbdea0