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:
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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){
|
||||
|
@ -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) );
|
||||
|
Reference in New Issue
Block a user