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

JNI: add sqlite3_blob_write() overload which accepts a java.nio.ByteBuffer. Cleanups in adjacent code.

FossilOrigin-Name: ca32af8542aa2725cc87f54541b19897556f610e4674edf9f22a84e3d4097a82
This commit is contained in:
stephan
2023-11-14 01:33:15 +00:00
parent ce2edab088
commit b481413d95
7 changed files with 205 additions and 58 deletions

View File

@ -2481,85 +2481,93 @@ S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)(
*/
struct S3JniNioArgs {
jobject jBuf; /* input - ByteBuffer */
jint iBegin; /* input - byte offset */
jint iN; /* input - byte count to bind */
jint iOffset; /* input - byte offset */
jint iHowMany; /* input - byte count to bind/read/write */
jint nBuf; /* output - jBuf's buffer size */
void * p; /* output - jBuf's buffer memory */
const void * pStart; /* output - offset of p to bind */
int iOutLen; /* output - number of bytes from pStart to bind */
const void * pStart; /* output - offset of p to bind/read/write */
int nOut; /* output - number of bytes from pStart to bind/read/write */
};
typedef struct S3JniNioArgs S3JniNioArgs;
static const S3JniNioArgs S3JniNioArgs_empty = {
0,0,0,0,0,0,0
};
/**
Internal helper for sqlite3_bind_nio_buffer() and
sqlite3_result_nio_buffer(). Populates pArgs and returns 0 on
success, non-0 if the operation should fail. The caller is
required to check for SJG.g.cByteBuffer!=0 before calling
this and reporting it in a way appropriate for that routine.
This function may assert() that SJG.g.cByteBuffer is not 0.
The (jBuffer, iBegin, iN) arguments are the (ByteBuffer, offset,
length) arguments to the bind/result method.
/*
** Internal helper for sqlite3_bind_nio_buffer() and
** sqlite3_result_nio_buffer(). Populates pArgs and returns 0 on
** success, non-0 if the operation should fail. The caller is required
** to check for SJG.g.cByteBuffer!=0 before calling this and reporting
** it in a way appropriate for that routine. This function may
** assert() that SJG.g.cByteBuffer is not 0.
**
** The (jBuffer, iOffset, iN) arguments are the (ByteBuffer, offset,
** length) arguments to the bind/result method.
**
** Returns 0 if everything looks to be in order, else some SQLITE_...
** result code
*/
static int s3jni_setup_nio_args(
JNIEnv *env, S3JniNioArgs * pArgs,
jobject jBuffer, jint iBegin, jint iN
jobject jBuffer, jint iOffset, jint iN
){
jlong iEnd = 0;
*pArgs = S3JniNioArgs_empty;
pArgs->jBuf = jBuffer;
pArgs->iBegin = iBegin;
pArgs->iN = iN;
pArgs->iOffset = iOffset;
pArgs->iHowMany = iN;
assert( SJG.g.cByteBuffer );
if( pArgs->iBegin<0 ){
return SQLITE_MISUSE;
if( pArgs->iOffset<0 ){
return SQLITE_ERROR
/* SQLITE_MISUSE would arguably fit better but we use
SQLITE_ERROR for consistency with the code documented for a
negative target blob offset in sqlite3_blob_read/write(). */;
}
s3jni_get_nio_buffer(pArgs->jBuf, &pArgs->p, &pArgs->nBuf);
if( !pArgs->p ){
return SQLITE_MISUSE;
}else if( pArgs->iBegin>=pArgs->nBuf ){
}else if( pArgs->iOffset>=pArgs->nBuf ){
pArgs->pStart = 0;
pArgs->iOutLen = 0;
pArgs->nOut = 0;
return 0;
}
assert( pArgs->nBuf > 0 );
assert( pArgs->iBegin < pArgs->nBuf );
iEnd = pArgs->iN<0 ? pArgs->nBuf - pArgs->iBegin : pArgs->iBegin + pArgs->iN;
if( iEnd>(jlong)pArgs->nBuf ) iEnd = pArgs->nBuf - pArgs->iBegin;
if( iEnd - pArgs->iBegin > (jlong)SQLITE_MAX_LENGTH ){
assert( pArgs->iOffset < pArgs->nBuf );
iEnd = pArgs->iHowMany<0
? pArgs->nBuf - pArgs->iOffset
: pArgs->iOffset + pArgs->iHowMany;
if( iEnd>(jlong)pArgs->nBuf ) iEnd = pArgs->nBuf - pArgs->iOffset;
if( iEnd - pArgs->iOffset > (jlong)SQLITE_MAX_LENGTH ){
return SQLITE_TOOBIG;
}
assert( pArgs->iBegin >= 0 );
assert( iEnd > pArgs->iBegin );
pArgs->pStart = pArgs->p + pArgs->iBegin;
pArgs->iOutLen = (int)(iEnd - pArgs->iBegin);
assert( pArgs->iOutLen > 0 );
assert( pArgs->iOffset >= 0 );
assert( iEnd > pArgs->iOffset );
pArgs->pStart = pArgs->p + pArgs->iOffset;
pArgs->nOut = (int)(iEnd - pArgs->iOffset);
assert( pArgs->nOut > 0 );
return 0;
}
S3JniApi(sqlite3_bind_nio_buffer(),jint,1bind_1nio_1buffer)(
JniArgsEnvClass, jobject jpStmt, jint ndx, jobject jBuffer,
jint iBegin, jint iN
jint iOffset, jint iN
){
sqlite3_stmt * pStmt = PtrGet_sqlite3_stmt(jpStmt);
S3JniNioArgs args;
int rc;
if( !pStmt || !SJG.g.cByteBuffer ) return SQLITE_MISUSE;
rc = s3jni_setup_nio_args(env, &args, jBuffer, iBegin, iN);
rc = s3jni_setup_nio_args(env, &args, jBuffer, iOffset, iN);
if(rc){
return rc;
}else if( !args.pStart || !args.iOutLen ){
}else if( !args.pStart || !args.nOut ){
return sqlite3_bind_null(pStmt, ndx);
}
assert( args.iOutLen>0 );
assert( args.nOut>0 );
assert( args.nBuf > 0 );
assert( args.pStart != 0 );
assert( (args.pStart + args.iOutLen) <= (args.p + args.nBuf) );
assert( (args.pStart + args.nOut) <= (args.p + args.nBuf) );
return sqlite3_bind_blob( pStmt, (int)ndx, args.pStart,
args.iOutLen, SQLITE_TRANSIENT );
args.nOut, SQLITE_TRANSIENT );
}
S3JniApi(sqlite3_bind_double(),jint,1bind_1double)(
@ -2797,6 +2805,33 @@ S3JniApi(sqlite3_blob_write(),jint,1blob_1write)(
return (jint)rc;
}
S3JniApi(sqlite3_blob_write_nio_buffer(),jint,1blob_1write_1nio_1buffer)(
JniArgsEnvClass, jlong jpBlob, jint iSrcOff, jobject jBB, jint iTgtOff, jint iHowMany
){
sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob);
S3JniNioArgs args;
int rc;
if( !b || !SJG.g.cByteBuffer ){
return SQLITE_MISUSE;
}else if( iTgtOff<0 || iSrcOff<0 ){
return SQLITE_ERROR
/* for consistency with underlying sqlite3_blob_write() */;
}else if( 0==iHowMany ){
return 0;
}
rc = s3jni_setup_nio_args(env, &args, jBB, iTgtOff, iHowMany);
if(rc){
return rc;
}else if( !args.pStart || !args.nOut ){
return 0;
}
assert( args.nOut>0 );
assert( args.nBuf > 0 );
assert( args.pStart != 0 );
assert( (args.pStart + args.nOut) <= (args.p + args.nBuf) );
return sqlite3_blob_write( b, args.pStart, (int)args.nOut, iSrcOff );
}
/* Central C-to-Java busy handler proxy. */
static int s3jni_busy_handler(void* pState, int n){
S3JniDb * const ps = (S3JniDb *)pState;
@ -3065,7 +3100,7 @@ S3JniApi(sqlite3_column_java_object(),jobject,1column_1java_1object)(
return rv;
}
S3JniApi(sqlite3_value_nio_buffer(),jobject,1column_1nio_1buffer)(
S3JniApi(sqlite3_column_nio_buffer(),jobject,1column_1nio_1buffer)(
JniArgsEnvClass, jobject jStmt, jint ndx
){
sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jStmt);
@ -4588,7 +4623,7 @@ S3JniApi(sqlite3_result_java_object(),void,1result_1java_1object)(
S3JniApi(sqlite3_result_nio_buffer(),void,1result_1nio_1buffer)(
JniArgsEnvClass, jobject jpCtx, jobject jBuffer,
jint iBegin, jint iN
jint iOffset, jint iN
){
sqlite3_context * pCx = PtrGet_sqlite3_context(jpCtx);
int rc;
@ -4601,9 +4636,9 @@ S3JniApi(sqlite3_result_nio_buffer(),void,1result_1nio_1buffer)(
);
return;
}
rc = s3jni_setup_nio_args(env, &args, jBuffer, iBegin, iN);
rc = s3jni_setup_nio_args(env, &args, jBuffer, iOffset, iN);
if(rc){
if( iBegin<0 ){
if( iOffset<0 ){
sqlite3_result_error(pCx, "Start index may not be negative.", -1);
}else if( SQLITE_TOOBIG==rc ){
sqlite3_result_error_toobig(pCx);
@ -4612,10 +4647,10 @@ S3JniApi(sqlite3_result_nio_buffer(),void,1result_1nio_1buffer)(
pCx, "Invalid arguments to sqlite3_result_nio_buffer().", -1
);
}
}else if( !args.pStart || !args.iOutLen ){
}else if( !args.pStart || !args.nOut ){
sqlite3_result_null(pCx);
}else{
sqlite3_result_blob(pCx, args.pStart, args.iOutLen, SQLITE_TRANSIENT);
sqlite3_result_blob(pCx, args.pStart, args.nOut, SQLITE_TRANSIENT);
}
}

View File

@ -1009,6 +1009,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1reopen
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write
(JNIEnv *, jclass, jlong, jbyteArray, jint);
/*
* Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_blob_write_nio_buffer
* Signature: (JILjava/nio/ByteBuffer;II)I
*/
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write_1nio_1buffer
(JNIEnv *, jclass, jlong, jint, jobject, jint, jint);
/*
* Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_busy_handler

View File

@ -119,13 +119,15 @@ public final class CApi {
<p>This routine returns false without side effects if the current
JNIEnv is not cached, else returns true, but this information is
primarily for testing of the JNI bindings and is not information
which client-level code can use to make any informed decisions.
which client-level code can use to make any informed
decisions. Its return type and semantics are not considered
stable and may change at any time.
*/
public static native boolean sqlite3_java_uncache_thread();
/**
Returns true if this JVM has JNI-level support for direct memory
access using java.nio.ByteBuffer, else returns false.
Returns true if this JVM has JNI-level support for C-level direct
memory access using java.nio.ByteBuffer, else returns false.
*/
public static native boolean sqlite3_jni_supports_nio();
@ -565,6 +567,49 @@ public final class CApi {
return sqlite3_blob_write(b.getNativePointer(), bytes, iOffset);
}
/**
An internal level of indirection in order to avoid having
overloaded names of sqlite3_blob_write() in the C API, as the
resulting mangled names are unwieldy. The public face of this
method is the sqlite3_blob_write() overload which takes a
java.nio.ByteBuffer.
*/
private static native int sqlite3_blob_write_nio_buffer(
@NotNull long ptrToBlob, int tgtOffset,
@NotNull java.nio.ByteBuffer src,
int srcOffset, int howMany
);
/**
Writes howMany bytes of memory from offset srcOffset of the src
buffer at position tgtOffset of b.
If howMany is negative then it's equivalent to the number of
bytes remaining starting at srcOffset. If the computed input
slice exceeds src's bounds, the slice is silently truncated.
*/
public static int sqlite3_blob_write(
@NotNull sqlite3_blob b, int tgtOffset,
@NotNull java.nio.ByteBuffer src,
int srcOffset, int howMany
){
return sqlite3_blob_write_nio_buffer(b.getNativePointer(), tgtOffset,
src, srcOffset, howMany);
}
/**
Convenience overload which writes all of src to the given offset
of b.
*/
public static int sqlite3_blob_write(
@NotNull sqlite3_blob b, int tgtOffset,
@NotNull java.nio.ByteBuffer src
){
return sqlite3_blob_write_nio_buffer(b.getNativePointer(), tgtOffset,
src, 0, -1);
}
private static native int sqlite3_busy_handler(
@NotNull long ptrToDb, @Nullable BusyHandlerCallback handler
);
@ -1787,7 +1832,8 @@ public final class CApi {
<ul>
<li>text is null: translates to a call to sqlite3_result_null()</li>
<li>text is null: translates to a call to {@link
#sqlite3_result_null}</li>
<li>text is too large: translates to a call to
{@link #sqlite3_result_error_toobig}</li>

View File

@ -228,4 +228,26 @@ public final class OutputPointer {
/** Sets the current value. */
public final void set(byte[] v){value = v;}
}
/**
Output pointer for use with native routines which return
blobs via java.nio.ByteBuffer.
See {@link org.sqlite.jni.capi.CApi#sqlite3_jni_supports_nio}
*/
public static final class ByteBuffer {
/**
This is public for ease of use. Accessors are provided for
consistency with the higher-level types.
*/
public java.nio.ByteBuffer value;
/** Initializes with the value null. */
public ByteBuffer(){this(null);}
/** Initializes with the value v. */
public ByteBuffer(java.nio.ByteBuffer v){value = v;}
/** Returns the current value. */
public final java.nio.ByteBuffer get(){return value;}
/** Sets the current value. */
public final void set(java.nio.ByteBuffer v){value = v;}
}
}

View File

@ -596,7 +596,8 @@ public class Tester1 implements Runnable {
final int expectTotal = buf.get(1) + buf.get(2) + buf.get(3);
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
affirm( SQLITE_MISUSE == sqlite3_bind_blob(stmt, 1, buf, -1, 0) );
affirm( SQLITE_ERROR == sqlite3_bind_blob(stmt, 1, buf, -1, 0),
"Buffer offset may not be negative." );
affirm( 0 == sqlite3_bind_blob(stmt, 1, buf, 1, 3) );
affirm( SQLITE_DONE == sqlite3_step(stmt) );
sqlite3_finalize(stmt);
@ -1742,6 +1743,41 @@ public class Tester1 implements Runnable {
affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" );
rc = sqlite3_blob_close(b);
affirm( 0==rc );
if( !sqlite3_jni_supports_nio() ){
outln("WARNING: skipping tests for ByteBuffer-using sqlite3_blob APIs ",
"because this platform lacks that support.");
sqlite3_close_v2(db);
return;
}
/* Sanity checks for the java.nio.ByteBuffer-taking overloads of
sqlite3_blob_read/write(). */
execSql(db, "UPDATE t SET a=zeroblob(10)");
b = sqlite3_blob_open(db, "main", "t", "a", 1, 1);
affirm( null!=b );
java.nio.ByteBuffer bb = java.nio.ByteBuffer.allocateDirect(10);
for( byte i = 0; i < 10; ++i ){
bb.put((int)i, (byte)(48+i & 0xff));
}
rc = sqlite3_blob_write(b, 1, bb);
affirm( rc==SQLITE_ERROR, "Because b length < (offset 1 + bb length)" );
rc = sqlite3_blob_write(b, -1, bb);
affirm( rc==SQLITE_ERROR, "Target offset may not be negative" );
rc = sqlite3_blob_write(b, 0, bb, -1, -1);
affirm( rc==SQLITE_ERROR, "Source offset may not be negative" );
rc = sqlite3_blob_write(b, 1, bb, 1, 8);
affirm( rc==0 );
// b's contents: 0 49 50 51 52 53 54 55 56 0
// ascii: 0 '1' '2' '3' '4' '5' '6' '7' '8' 0
byte br[] = new byte[10];
rc = sqlite3_blob_read( b, br, 0 );
sqlite3_blob_close(b);
affirm( rc==0 );
affirm( 0==br[0] );
affirm( 0==br[9] );
for( int i = 1; i < 9; ++i ){
affirm( br[i] == 48 + i );
}
sqlite3_close_v2(db);
}