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

JNI: add sqlite3_result_nio_buffer() and tests. Discover that we cannot create sensible sqlite3_column_nio_buffer() or sqlite3_value_nio_buffer() counterparts because of ByteBuffer interface limitations.

FossilOrigin-Name: 44b4df01ff86841fb85b6295cbada422c6ba8a32a420a2e840e2d607b6c90164
This commit is contained in:
stephan
2023-11-13 18:35:37 +00:00
parent 7f7d7bea01
commit 87c407da18
6 changed files with 251 additions and 69 deletions

View File

@ -882,7 +882,7 @@ static jbyte * s3jni__jbyteArray_bytes2(JNIEnv * const env, jbyteArray jBA, jsiz
** required so cannot check for that violation. The caller is required
** to ensure that any to-be-bind()ed range fits within SQLITE_LIMIT.
*/
/*static*/ void s3jni__get_nio_buffer(JNIEnv * const env, jobject jbb, void **pBuf, jint * pN ){
static void s3jni__get_nio_buffer(JNIEnv * const env, jobject jbb, void **pBuf, jint * pN ){
*pBuf = 0;
*pN = 0;
if( jbb ){
@ -2433,32 +2433,90 @@ S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)(
return (jint)rc;
}
/**
Helper for use with s3jni_setup_nio_args().
*/
struct S3JniNioArgs {
jobject jBuf; /* input - ByteBuffer */
jint iBegin; /* input - byte offset */
jint iN; /* input - byte count to bind */
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 */
};
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.
*/
static int s3jni_setup_nio_args(
JNIEnv *env, S3JniNioArgs * pArgs,
jobject jBuffer, jint iBegin, jint iN
){
jlong iEnd = 0;
*pArgs = S3JniNioArgs_empty;
pArgs->jBuf = jBuffer;
pArgs->iBegin = iBegin;
pArgs->iN = iN;
assert( SJG.g.cByteBuffer );
if( pArgs->iBegin<0 ){
return SQLITE_MISUSE;
}
s3jni_get_nio_buffer(pArgs->jBuf, &pArgs->p, &pArgs->nBuf);
if( !pArgs->p ){
return SQLITE_MISUSE;
}else if( pArgs->iBegin>=pArgs->nBuf ){
pArgs->pStart = 0;
pArgs->iOutLen = 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 ){
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 );
return 0;
}
S3JniApi(sqlite3_bind_nio_buffer(),jint,1bind_1nio_1buffer)(
JniArgsEnvClass, jobject jpStmt, jint ndx, jobject jBuffer,
jint iBegin, jint iN
){
sqlite3_stmt * pStmt = PtrGet_sqlite3_stmt(jpStmt);
void * pBuf = 0;
jint nBuf = 0;
jlong iEnd = 0;
if( !SJG.g.cByteBuffer || !pStmt || iBegin<0 ){
return (jint)SQLITE_MISUSE;
S3JniNioArgs args;
int rc;
if( !pStmt || !SJG.g.cByteBuffer ) return SQLITE_MISUSE;
rc = s3jni_setup_nio_args(env, &args, jBuffer, iBegin, iN);
if(rc){
return rc;
}else if( !args.pStart || !args.iOutLen ){
return sqlite3_bind_null(pStmt, ndx);
}
s3jni_get_nio_buffer(jBuffer, &pBuf, &nBuf);
if( !pBuf || iBegin>=nBuf ){
return (jint)sqlite3_bind_null(pStmt, ndx);
}
assert( nBuf > 0 );
assert( iBegin < nBuf );
iEnd = iN<0 ? nBuf - iBegin : iBegin + iN;
if( iEnd>(jlong)nBuf ) iEnd = nBuf-iBegin;
if( iEnd-iBegin >(jlong)SQLITE_MAX_LENGTH ){
return SQLITE_MISUSE;
}
assert( iBegin>=0 );
assert( iEnd > iBegin );
return (jint)sqlite3_bind_blob(pStmt, (int)ndx, pBuf + iBegin,
(int)(iEnd - iBegin), SQLITE_TRANSIENT);
assert( args.iOutLen>0 );
assert( args.nBuf > 0 );
assert( args.pStart != 0 );
assert( (args.pStart + args.iOutLen) <= (args.p + args.nBuf) );
return sqlite3_bind_blob( pStmt, (int)ndx, args.pStart,
args.iOutLen, SQLITE_TRANSIENT );
}
S3JniApi(sqlite3_bind_double(),jint,1bind_1double)(
@ -4470,6 +4528,40 @@ 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
){
sqlite3_context * pCx = PtrGet_sqlite3_context(jpCtx);
int rc;
S3JniNioArgs args;
if( !pCx ){
return;
}else if( !SJG.g.cByteBuffer ){
sqlite3_result_error(
pCx, "This JVM does not support JNI access to ByteBuffers.", -1
);
return;
}
rc = s3jni_setup_nio_args(env, &args, jBuffer, iBegin, iN);
if(rc){
if( iBegin<0 ){
sqlite3_result_error(pCx, "Start index may not be negative.", -1);
}else if( SQLITE_TOOBIG==rc ){
sqlite3_result_error_toobig(pCx);
}else{
sqlite3_result_error(
pCx, "Invalid arguments to sqlite3_result_nio_buffer().", -1
);
}
}else if( !args.pStart || !args.iOutLen ){
sqlite3_result_null(pCx);
}else{
sqlite3_result_blob(pCx, args.pStart, args.iOutLen, SQLITE_TRANSIENT);
}
}
S3JniApi(sqlite3_result_null(),void,1result_1null)(
JniArgsEnvClass, jobject jpCx
){

View File

@ -1729,6 +1729,14 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1int64
JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1java_1object
(JNIEnv *, jclass, jobject, jobject);
/*
* Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_result_nio_buffer
* Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Ljava/nio/ByteBuffer;II)V
*/
JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1nio_1buffer
(JNIEnv *, jclass, jobject, jobject, jint, jint);
/*
* Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_result_null

View File

@ -295,16 +295,16 @@ public final class CApi {
range is silently truncated to fit the buffer.
If any of the following are true, this function behaves like
sqlite3_bind_null(): the buffer is null, beginPos is past the end
of the buffer, howMany is 0, or the calculated slice of the blob
has a length of 0.
sqlite3_bind_null(): the buffer is null, beginPos is at or past
the end of the buffer, howMany is 0, or the calculated slice of
the blob has a length of 0.
If ndx is out of range, it returns SQLITE_RANGE, as documented
for sqlite3_bind_blob(). If any other arguments are invalid or
if sqlite3_jni_supports_nio() is false then SQLITE_MISUSE is
for sqlite3_bind_blob(). If beginPos is negative or if
sqlite3_jni_supports_nio() returns false then SQLITE_MISUSE is
returned. Note that this function is bound (as it were) by the
SQLITE_LIMIT_LENGTH constraint and SQLITE_MISUSE is returned if
that's violated.
SQLITE_LIMIT_LENGTH constraint and SQLITE_TOOBIG is returned if
the resulting slice of the buffer exceeds that limit.
This function does not modify the buffer's streaming-related
cursors.
@ -318,6 +318,10 @@ 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(
@ -1560,6 +1564,39 @@ public final class CApi {
@NotNull sqlite3_context cx, @NotNull Object o
);
/**
Similar to sqlite3_bind_nio_buffer(), this works like
sqlite3_result_blob() but accepts a java.nio.ByteBuffer as its
input source. See sqlite3_bind_nio_buffer() for the semantics of
the second and subsequent arguments.
If cx is null then this function will silently fail. If
sqlite3_jni_supports_nio() returns false or iBegin is negative,
an error result is set. If (begin+n) extends beyond the end of
the buffer, it is silently truncated to fit.
If any of the following apply, this function behaves like
sqlite3_result_null(): the blob is null, the resulting slice of
the blob is empty.
If the resulting slice of the buffer exceeds SQLITE_LIMIT_LENGTH
then this function behaves like sqlite3_result_error_toobig().
*/
public static native void sqlite3_result_nio_buffer(
@NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob,
int begin, int n
);
/**
Convenience overload which uses the whole input object
as the result blob content.
*/
public static void sqlite3_result_nio_buffer(
@NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob
){
sqlite3_result_nio_buffer(cx, blob, 0, -1);
}
public static native void sqlite3_result_null(
@NotNull sqlite3_context cx
);
@ -1654,6 +1691,27 @@ public final class CApi {
sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length));
}
/**
Convenience overload which behaves like
sqlite3_result_nio_buffer().
*/
public static void sqlite3_result_blob(
@NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob,
int begin, int n
){
sqlite3_result_nio_buffer(cx, blob, begin, n);
}
/**
Convenience overload which behaves like the two-argument overload of
sqlite3_result_nio_buffer().
*/
public static void sqlite3_result_blob(
@NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob
){
sqlite3_result_nio_buffer(cx, blob);
}
/**
Binds the given text using C's sqlite3_result_blob64() unless:

View File

@ -39,12 +39,12 @@ import java.util.concurrent.Future;
@interface SingleThreadOnly{}
/**
Annotation for Tester1 tests which must only be run if JNI-level support for
java.nio.Buffer is available.
Annotation for Tester1 tests which must only be run if
sqlite3_jni_supports_nio() is true.
*/
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
@interface RequiresNioBuffer{}
@interface RequiresJniNio{}
public class Tester1 implements Runnable {
//! True when running in multi-threaded mode.
@ -565,42 +565,65 @@ public class Tester1 implements Runnable {
sqlite3_close_v2(db);
}
@RequiresNioBuffer
@RequiresJniNio
private void testBindByteBuffer(){
/* TODO: these tests need to be much more extensive to check the
begin/end range handling. */
sqlite3 db = createNewDb();
execSql(db, "CREATE TABLE t(a)");
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocateDirect(10);
final java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocateDirect(10);
buf.put((byte)0x31)/*note that we'll skip this one*/
.put((byte)0x32)
.put((byte)0x33)
.put((byte)0x34);
int rc = sqlite3_bind_blob(stmt, 1, buf, -1, 0);
affirm( SQLITE_MISUSE==rc );
rc = sqlite3_bind_blob(stmt, 1, buf, 1, 3);
affirm( 0==rc );
rc = sqlite3_step(stmt);
affirm(SQLITE_DONE == rc);
.put((byte)0x34)
.put((byte)0x35)/*we'll skip this one too*/;
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( 0 == sqlite3_bind_blob(stmt, 1, buf, 1, 3) );
affirm( SQLITE_DONE == sqlite3_step(stmt) );
sqlite3_finalize(stmt);
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
int n = 0;
stmt = prepare(db, "SELECT a FROM t;");
int total = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
byte[] blob = sqlite3_column_blob(stmt, 0);
affirm(3 == blob.length);
int i = 0;
for(byte b : blob){
affirm( i<=3 );
affirm(b == buf.get(1 + i++));
total += b;
}
++n;
affirm( SQLITE_ROW == sqlite3_step(stmt) );
byte blob[] = sqlite3_column_blob(stmt, 0);
affirm(3 == blob.length);
int i = 0;
for(byte b : blob){
affirm( i<=3 );
affirm(b == buf.get(1 + i++));
total += b;
}
affirm( SQLITE_DONE == sqlite3_step(stmt) );
sqlite3_finalize(stmt);
affirm(1 == n);
affirm(total == 0x32 + 0x33 + 0x34);
/* TODO: these tests need to be much more extensive to check the
begin range handling. */
affirm(total == expectTotal);
SQLFunction func =
new ScalarFunction(){
public void xFunc(sqlite3_context cx, sqlite3_value[] args){
sqlite3_result_blob(cx, buf, 1, 3);
}
};
affirm( 0 == sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func) );
stmt = prepare(db, "SELECT myfunc()");
affirm( SQLITE_ROW == sqlite3_step(stmt) );
blob = sqlite3_column_blob(stmt, 0);
affirm(3 == blob.length);
i = 0;
total = 0;
for(byte b : blob){
affirm( i<=3 );
affirm(b == buf.get(1 + i++));
total += b;
}
affirm( SQLITE_DONE == sqlite3_step(stmt) );
sqlite3_finalize(stmt);
affirm(total == expectTotal);
sqlite3_close_v2(db);
}
@ -1924,9 +1947,10 @@ public class Tester1 implements Runnable {
if( forceFail ){
testMethods.add(m);
}
}else if( m.isAnnotationPresent( RequiresNioBuffer.class )
}else if( m.isAnnotationPresent( RequiresJniNio.class )
&& !sqlite3_jni_supports_nio() ){
outln("Skipping test for lack JNI nio.Buffer support: ",name,"()\n");
outln("Skipping test for lack of JNI java.nio.ByteBuffer support: ",
name,"()\n");
++nSkipped;
}else if( !m.isAnnotationPresent( ManualTest.class ) ){
if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){