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

JNI: add sqlite3_column_nio_buffer() and sqlite3_value_nio_buffer() using an only-slightly roundabout approach to creating properly-sized ByteBuffer objects.

FossilOrigin-Name: efbc82b218d26b7ca9b881da69d5fd14d22b5211fbd85a835da50e5bfde3d160
This commit is contained in:
stephan
2023-11-13 23:11:10 +00:00
parent 87c407da18
commit ce2edab088
6 changed files with 173 additions and 31 deletions

View File

@ -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
@ -1093,6 +1095,47 @@ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p,
return rv;
}
/*
** 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
@ -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;
}
}

View File

@ -1105,6 +1105,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1bytes16
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1count
(JNIEnv *, jclass, jlong);
/*
* 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
@ -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
@ -2105,6 +2113,14 @@ JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1int64
JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1java_1object
(JNIEnv *, jclass, jlong);
/*
* 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

View File

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

View File

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