aboutsummaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/jni/src/c/sqlite3-jni.c90
-rw-r--r--ext/jni/src/c/sqlite3-jni.h24
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/CApi.java48
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/Tester1.java22
4 files changed, 163 insertions, 21 deletions
diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c
index e04b3ab5f..4028505a8 100644
--- a/ext/jni/src/c/sqlite3-jni.c
+++ b/ext/jni/src/c/sqlite3-jni.c
@@ -15,13 +15,14 @@
/*
** If you found this comment by searching the code for
-** CallStaticObjectMethod then you're the victim of an OpenJDK bug:
+** CallStaticObjectMethod because it appears in console output then
+** you're probably the victim of an OpenJDK bug:
**
** https://bugs.openjdk.org/browse/JDK-8130659
**
-** It's known to happen with OpenJDK v8 but not with v19.
-**
-** This code does not use JNI's CallStaticObjectMethod().
+** It's known to happen with OpenJDK v8 but not with v19. It was
+** triggered by this code long before it made any use of
+** CallStaticObjectMethod().
*/
/*
@@ -664,6 +665,7 @@ struct S3JniGlobalType {
ByteBuffer is available (which we determine during static init).
*/
jclass cByteBuffer /* global ref to java.nio.ByteBuffer */;
+ jmethodID byteBufferAlloc/* ByteBuffer.allocateDirect() */;
} g;
/*
** The list of Java-side auto-extensions
@@ -1094,6 +1096,47 @@ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p,
}
/*
+** Creates a new ByteBuffer instance with a capacity of n. assert()s
+** that SJG.g.cByteBuffer is not 0 and n>0.
+*/
+static jobject s3jni__new_ByteBuffer(JNIEnv * const env, int n){
+ jobject rv = 0;
+ assert( SJG.g.cByteBuffer );
+ assert( SJG.g.byteBufferAlloc );
+ assert( n > 0 );
+ rv = (*env)->CallStaticObjectMethod(env, SJG.g.cByteBuffer,
+ SJG.g.byteBufferAlloc, (jint)n);
+ S3JniIfThrew {
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ }
+ s3jni_oom_check( rv );
+ return rv;
+}
+
+/*
+** If n>0 and sqlite3_jni_supports_nio() is true then this creates a
+** new ByteBuffer object and copies n bytes from p to it. Returns NULL
+** if n is 0, sqlite3_jni_supports_nio() is false, or on allocation
+** error (unless fatal alloc failures are enabled).
+*/
+static jobject s3jni__blob_to_ByteBuffer(JNIEnv * const env,
+ const void * p, int n){
+ jobject rv = NULL;
+ assert( n >= 0 );
+ if( 0==n || !SJG.g.cByteBuffer ){
+ return NULL;
+ }
+ rv = s3jni__new_ByteBuffer(env, n);
+ if( rv ){
+ void * tgt = (*env)->GetDirectBufferAddress(env, rv);
+ memcpy(tgt, p, (size_t)n);
+ }
+ return rv;
+}
+
+
+/*
** Requires jx to be a Throwable. Calls its toString() method and
** returns its value converted to a UTF-8 string. The caller owns the
** returned string and must eventually sqlite3_free() it. Returns 0
@@ -1936,7 +1979,7 @@ static void udf_unargs(JNIEnv *env, jobject jCx, int argc, jobjectArray jArgv){
NativePointerHolder_set(S3JniNph(sqlite3_context), jCx, 0);
for( ; i < argc; ++i ){
jobject jsv = (*env)->GetObjectArrayElement(env, jArgv, i);
- assert(jsv);
+ assert(jsv && "Someone illegally modified a UDF argument array.");
NativePointerHolder_set(S3JniNph(sqlite3_value), jsv, 0);
}
}
@@ -3022,6 +3065,21 @@ S3JniApi(sqlite3_column_java_object(),jobject,1column_1java_1object)(
return rv;
}
+S3JniApi(sqlite3_value_nio_buffer(),jobject,1column_1nio_1buffer)(
+ JniArgsEnvClass, jobject jStmt, jint ndx
+){
+ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jStmt);
+ jobject rv = 0;
+ if( stmt ){
+ const void * const p = sqlite3_column_blob(stmt, (int)ndx);
+ if( p ){
+ const int n = sqlite3_column_bytes(stmt, (int)ndx);
+ rv = s3jni__blob_to_ByteBuffer(env, p, n);
+ }
+ }
+ return rv;
+}
+
S3JniApi(sqlite3_column_text(),jbyteArray,1column_1text)(
JniArgsEnvClass, jobject jpStmt, jint ndx
){
@@ -5090,6 +5148,21 @@ S3JniApi(sqlite3_value_java_object(),jobject,1value_1java_1object)(
: 0;
}
+S3JniApi(sqlite3_value_nio_buffer(),jobject,1value_1nio_1buffer)(
+ JniArgsEnvClass, jobject jVal
+){
+ sqlite3_value * const sv = PtrGet_sqlite3_value(jVal);
+ jobject rv = 0;
+ if( sv ){
+ const void * const p = sqlite3_value_blob(sv);
+ if( p ){
+ const int n = sqlite3_value_bytes(sv);
+ rv = s3jni__blob_to_ByteBuffer(env, p, n);
+ }
+ }
+ return rv;
+}
+
S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)(
JniArgsEnvClass, jlong jpSVal
){
@@ -6096,10 +6169,15 @@ Java_org_sqlite_jni_capi_CApi_init(JniArgsEnvClass){
unsigned char buf[16] = {0};
jobject bb = (*env)->NewDirectByteBuffer(env, buf, 16);
if( bb ){
- SJG.g.cByteBuffer = (*env)->GetObjectClass(env, bb);
+ SJG.g.cByteBuffer = S3JniRefGlobal((*env)->GetObjectClass(env, bb));
+ SJG.g.byteBufferAlloc = (*env)->GetStaticMethodID(
+ env, SJG.g.cByteBuffer, "allocateDirect", "(I)Ljava/nio/ByteBuffer;"
+ );
+ S3JniExceptionIsFatal("Error getting ByteBuffer.allocateDirect() method.");
S3JniUnrefLocal(bb);
}else{
SJG.g.cByteBuffer = 0;
+ SJG.g.byteBufferAlloc = 0;
}
}
diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h
index bf1cc56d1..c9034dbee 100644
--- a/ext/jni/src/c/sqlite3-jni.h
+++ b/ext/jni/src/c/sqlite3-jni.h
@@ -1107,6 +1107,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1count
/*
* Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_database_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1database_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_column_decltype
* Signature: (JI)Ljava/lang/String;
*/
@@ -1155,11 +1163,11 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1name
/*
* Class: org_sqlite_jni_capi_CApi
- * Method: sqlite3_column_database_name
- * Signature: (JI)Ljava/lang/String;
+ * Method: sqlite3_column_nio_buffer
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Ljava/nio/ByteBuffer;
*/
-JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1database_1name
- (JNIEnv *, jclass, jlong, jint);
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1nio_1buffer
+ (JNIEnv *, jclass, jobject, jint);
/*
* Class: org_sqlite_jni_capi_CApi
@@ -2107,6 +2115,14 @@ JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1java_1ob
/*
* Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_nio_buffer
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_value;)Ljava/nio/ByteBuffer;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1nio_1buffer
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_value_nochange
* Signature: (J)I
*/
diff --git a/ext/jni/src/org/sqlite/jni/capi/CApi.java b/ext/jni/src/org/sqlite/jni/capi/CApi.java
index d3d434f7d..cd2a09e8c 100644
--- a/ext/jni/src/org/sqlite/jni/capi/CApi.java
+++ b/ext/jni/src/org/sqlite/jni/capi/CApi.java
@@ -318,10 +318,6 @@ public final class CApi {
buffers and the only such class offered by Java is (apparently)
ByteBuffer.
- Design note: there are no sqlite3_column_nio_buffer() and
- sqlite3_value_nio_buffer() counterparts because the ByteBuffer
- interface does not enable sensible implementations of those.
-
@see https://docs.oracle.com/javase/8/docs/api/java/nio/Buffer.html
*/
public static native int sqlite3_bind_nio_buffer(
@@ -646,6 +642,15 @@ public final class CApi {
return sqlite3_column_count(stmt.getNativePointer());
}
+ private static native String sqlite3_column_database_name(@NotNull long ptrToStmt, int ndx);
+
+ /**
+ Only available if built with SQLITE_ENABLE_COLUMN_METADATA.
+ */
+ public static String sqlite3_column_database_name(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_database_name(stmt.getNativePointer(), ndx);
+ }
+
private static native String sqlite3_column_decltype(@NotNull long ptrToStmt, int ndx);
public static String sqlite3_column_decltype(@NotNull sqlite3_stmt stmt, int ndx){
@@ -700,14 +705,15 @@ public final class CApi {
return sqlite3_column_name(stmt.getNativePointer(), ndx);
}
- private static native String sqlite3_column_database_name(@NotNull long ptrToStmt, int ndx);
-
/**
- Only available if built with SQLITE_ENABLE_COLUMN_METADATA.
+ A variant of sqlite3_column_blob() which returns the blob as a
+ ByteBuffer object. Returns null if its argument is null, if
+ sqlite3_jni_supports_nio() is false, or if sqlite3_column_blob()
+ would return null for the same inputs.
*/
- public static String sqlite3_column_database_name(@NotNull sqlite3_stmt stmt, int ndx){
- return sqlite3_column_database_name(stmt.getNativePointer(), ndx);
- }
+ public static native java.nio.ByteBuffer sqlite3_column_nio_buffer(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
private static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx);
@@ -1418,6 +1424,15 @@ public final class CApi {
If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined,
this acts as a proxy for C's sqlite3_preupdate_new(), else it
returns SQLITE_MISUSE with no side effects.
+
+ WARNING: client code _must not_ hold a reference to the returned
+ sqlite3_value object beyond the scope of the preupdate hook in
+ which this function is called. Doing so will leave the client
+ holding a stale pointer, the address of which could point to
+ anything at all after the pre-update hook is complete. This API
+ has no way to record such objects and clear/invalidate them at
+ the end of a pre-update hook. We "could" add infrastructure to do
+ so, but would require significant levels of bookkeeping.
*/
public static int sqlite3_preupdate_new(@NotNull sqlite3 db, int col,
@NotNull OutputPointer.sqlite3_value out){
@@ -1441,6 +1456,9 @@ public final class CApi {
If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined,
this acts as a proxy for C's sqlite3_preupdate_old(), else it
returns SQLITE_MISUSE with no side effects.
+
+ WARNING: see warning in sqlite3_preupdate_new() regarding the
+ potential for stale sqlite3_value handles.
*/
public static int sqlite3_preupdate_old(@NotNull sqlite3 db, int col,
@NotNull OutputPointer.sqlite3_value out){
@@ -2110,6 +2128,16 @@ public final class CApi {
return type.isInstance(o) ? (T)o : null;
}
+ /**
+ A variant of sqlite3_column_blob() which returns the blob as a
+ ByteBuffer object. Returns null if its argument is null, if
+ sqlite3_jni_supports_nio() is false, or if sqlite3_value_blob()
+ would return null for the same input.
+ */
+ public static native java.nio.ByteBuffer sqlite3_value_nio_buffer(
+ @NotNull sqlite3_value v
+ );
+
private static native int sqlite3_value_nochange(@NotNull long ptrToValue);
public static int sqlite3_value_nochange(@NotNull sqlite3_value v){
diff --git a/ext/jni/src/org/sqlite/jni/capi/Tester1.java b/ext/jni/src/org/sqlite/jni/capi/Tester1.java
index b50e3eaac..9e9f9a884 100644
--- a/ext/jni/src/org/sqlite/jni/capi/Tester1.java
+++ b/ext/jni/src/org/sqlite/jni/capi/Tester1.java
@@ -494,6 +494,7 @@ public class Tester1 implements Runnable {
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
StringBuilder sbuf = new StringBuilder();
n = 0;
+ final boolean tryNio = sqlite3_jni_supports_nio();
while( SQLITE_ROW == sqlite3_step(stmt) ){
final sqlite3_value sv = sqlite3_value_dup(sqlite3_column_value(stmt,0));
final String txt = sqlite3_column_text16(stmt, 0);
@@ -508,6 +509,15 @@ public class Tester1 implements Runnable {
StandardCharsets.UTF_8)) );
affirm( txt.length() == sqlite3_value_bytes16(sv)/2 );
affirm( txt.equals(sqlite3_value_text16(sv)) );
+ if( tryNio ){
+ java.nio.ByteBuffer bu = sqlite3_value_nio_buffer(sv);
+ byte ba[] = sqlite3_value_blob(sv);
+ affirm( ba.length == bu.capacity() );
+ int i = 0;
+ for( byte b : ba ){
+ affirm( b == bu.get(i++) );
+ }
+ }
sqlite3_value_free(sv);
++n;
}
@@ -570,6 +580,10 @@ public class Tester1 implements Runnable {
/* TODO: these tests need to be much more extensive to check the
begin/end range handling. */
+ java.nio.ByteBuffer zeroCheck =
+ java.nio.ByteBuffer.allocateDirect(0);
+ affirm( null != zeroCheck );
+ zeroCheck = null;
sqlite3 db = createNewDb();
execSql(db, "CREATE TABLE t(a)");
@@ -590,11 +604,17 @@ public class Tester1 implements Runnable {
int total = 0;
affirm( SQLITE_ROW == sqlite3_step(stmt) );
byte blob[] = sqlite3_column_blob(stmt, 0);
+ java.nio.ByteBuffer nioBlob =
+ sqlite3_column_nio_buffer(stmt, 0);
affirm(3 == blob.length);
+ affirm(blob.length == nioBlob.capacity());
+ affirm(blob.length == nioBlob.limit());
int i = 0;
for(byte b : blob){
affirm( i<=3 );
- affirm(b == buf.get(1 + i++));
+ affirm(b == buf.get(1 + i));
+ affirm(b == nioBlob.get(i));
+ ++i;
total += b;
}
affirm( SQLITE_DONE == sqlite3_step(stmt) );