From: stephan Date: Mon, 13 Nov 2023 14:58:37 +0000 (+0000) Subject: JNI: add sqlite3_bind_nio_buffer() and initial tests for binding ByteBuffer objects... X-Git-Tag: version-3.45.0~170 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7f7d7bea01e5782b566854ec3fc03c1b65dc52a5;p=thirdparty%2Fsqlite.git JNI: add sqlite3_bind_nio_buffer() and initial tests for binding ByteBuffer objects as blobs on JVMs which have JNI support for nio buffers. FossilOrigin-Name: b10ce1ef82d84726fbf6a8f624d6530f84fefb505f7868b4a0ea910fed7a877f --- diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index fafb2ab5fd..e3814825b1 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -870,6 +870,31 @@ static jbyte * s3jni__jbyteArray_bytes2(JNIEnv * const env, jbyteArray jBA, jsiz #define s3jni_jbyteArray_commit(jByteArray,jBytes) \ if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_COMMIT) +/* +** If jbb is-a java.nio.Buffer object and the JNI environment +** supports it, *pBuf is set to the buffer's memory and *pN is set to +** its length. If jbb is NULL, not a Buffer, or the JNI environment +** does not support that operation, *pBuf is set to 0 and *pN is set +** to 0. +** +** Note that the length of the buffer can be larger than SQLITE_LIMIT +** but this function does not know what byte range of the buffer is +** 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 ){ + *pBuf = 0; + *pN = 0; + if( jbb ){ + *pBuf = (*env)->GetDirectBufferAddress(env, jbb); + *pN = *pBuf ? (jint)(*env)->GetDirectBufferCapacity(env, jbb) : 0 + /* why the Java limits the buffer length to int but the JNI API + uses a jlong for the length is a mystery. */; + } +} +#define s3jni_get_nio_buffer(JOBJ,vpOut,jpOut) \ + s3jni__get_nio_buffer(env,(JOBJ),(vpOut),(jpOut)) + /* ** Returns the current JNIEnv object. Fails fatally if it cannot find ** the object. @@ -1479,13 +1504,13 @@ static void * NativePointerHolder__get(JNIEnv * env, jobject jNph, ** argument is a Java sqlite3 object, as this operation only has void ** pointers to work with. */ -#define PtrGet_T(T,OBJ) (T*)NativePointerHolder_get(OBJ, S3JniNph(T)) -#define PtrGet_sqlite3(OBJ) PtrGet_T(sqlite3, OBJ) -#define PtrGet_sqlite3_backup(OBJ) PtrGet_T(sqlite3_backup, OBJ) -#define PtrGet_sqlite3_blob(OBJ) PtrGet_T(sqlite3_blob, OBJ) -#define PtrGet_sqlite3_context(OBJ) PtrGet_T(sqlite3_context, OBJ) -#define PtrGet_sqlite3_stmt(OBJ) PtrGet_T(sqlite3_stmt, OBJ) -#define PtrGet_sqlite3_value(OBJ) PtrGet_T(sqlite3_value, OBJ) +#define PtrGet_T(T,JOBJ) (T*)NativePointerHolder_get((JOBJ), S3JniNph(T)) +#define PtrGet_sqlite3(JOBJ) PtrGet_T(sqlite3, (JOBJ)) +#define PtrGet_sqlite3_backup(JOBJ) PtrGet_T(sqlite3_backup, (JOBJ)) +#define PtrGet_sqlite3_blob(JOBJ) PtrGet_T(sqlite3_blob, (JOBJ)) +#define PtrGet_sqlite3_context(JOBJ) PtrGet_T(sqlite3_context, (JOBJ)) +#define PtrGet_sqlite3_stmt(JOBJ) PtrGet_T(sqlite3_stmt, (JOBJ)) +#define PtrGet_sqlite3_value(JOBJ) PtrGet_T(sqlite3_value, (JOBJ)) /* ** LongPtrGet_T(X,Y) expects X to be an unqualified sqlite3 struct ** type name and Y to be a native pointer to such an object in the @@ -1505,12 +1530,12 @@ static void * NativePointerHolder__get(JNIEnv * env, jobject jNph, ** a difference of microseconds (i.e. below our testing measurement ** threshold) might add up. */ -#define LongPtrGet_T(T,JLongAsPtr) (T*)((intptr_t)(JLongAsPtr)) -#define LongPtrGet_sqlite3(JLongAsPtr) LongPtrGet_T(sqlite3,JLongAsPtr) -#define LongPtrGet_sqlite3_backup(JLongAsPtr) LongPtrGet_T(sqlite3_backup,JLongAsPtr) -#define LongPtrGet_sqlite3_blob(JLongAsPtr) LongPtrGet_T(sqlite3_blob,JLongAsPtr) -#define LongPtrGet_sqlite3_stmt(JLongAsPtr) LongPtrGet_T(sqlite3_stmt,JLongAsPtr) -#define LongPtrGet_sqlite3_value(JLongAsPtr) LongPtrGet_T(sqlite3_value,JLongAsPtr) +#define LongPtrGet_T(T,JLongAsPtr) (T*)((intptr_t)((JLongAsPtr))) +#define LongPtrGet_sqlite3(JLongAsPtr) LongPtrGet_T(sqlite3,(JLongAsPtr)) +#define LongPtrGet_sqlite3_backup(JLongAsPtr) LongPtrGet_T(sqlite3_backup,(JLongAsPtr)) +#define LongPtrGet_sqlite3_blob(JLongAsPtr) LongPtrGet_T(sqlite3_blob,(JLongAsPtr)) +#define LongPtrGet_sqlite3_stmt(JLongAsPtr) LongPtrGet_T(sqlite3_stmt,(JLongAsPtr)) +#define LongPtrGet_sqlite3_value(JLongAsPtr) LongPtrGet_T(sqlite3_value,(JLongAsPtr)) /* ** Extracts the new S3JniDb instance from the free-list, or allocates ** one if needed, associates it with pDb, and returns. Returns NULL @@ -2408,6 +2433,34 @@ S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)( return (jint)rc; } +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; + } + 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); +} + S3JniApi(sqlite3_bind_double(),jint,1bind_1double)( JniArgsEnvClass, jlong jpStmt, jint ndx, jdouble val ){ diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 51d49bba3c..f306c53fcb 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -881,6 +881,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1int64 JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1java_1object (JNIEnv *, jclass, jlong, jint, jobject); +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_bind_nio_buffer + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;ILjava/nio/ByteBuffer;II)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1nio_1buffer + (JNIEnv *, jclass, jobject, jint, jobject, jint, jint); + /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_bind_null diff --git a/ext/jni/src/org/sqlite/jni/capi/CApi.java b/ext/jni/src/org/sqlite/jni/capi/CApi.java index 2dc238ce3e..569d8f4a97 100644 --- a/ext/jni/src/org/sqlite/jni/capi/CApi.java +++ b/ext/jni/src/org/sqlite/jni/capi/CApi.java @@ -215,7 +215,7 @@ public final class CApi { If n is negative, SQLITE_MISUSE is returned. If n>data.length then n is silently truncated to data.length. */ - static int sqlite3_bind_blob( + public static int sqlite3_bind_blob( @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n ){ return sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, n); @@ -229,6 +229,28 @@ public final class CApi { : sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, data.length); } + /** + Convenience overload which is a simple proxy for + sqlite3_bind_nio_buffer(). + */ + public static int sqlite3_bind_blob( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data, + int begin, int n + ){ + return sqlite3_bind_nio_buffer(stmt, ndx, data, begin, n); + } + + /** + Convenience overload which is equivalant to passing its arguments + to sqlite3_bind_nio_buffer() with the values 0 and -1 for the + final two arguments. + */ + public static int sqlite3_bind_blob( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data + ){ + return sqlite3_bind_nio_buffer(stmt, ndx, data, 0, -1); + } + private static native int sqlite3_bind_double( @NotNull long ptrToStmt, int ndx, double v ); @@ -261,6 +283,57 @@ public final class CApi { @NotNull long ptrToStmt, int ndx, @Nullable Object o ); + /** + Binds the contents of the given buffer object as a blob. + + The byte range of the buffer may be restricted by providing a + start index and a number of bytes. beginPos may not be negative + but a negative howMany is interpretated as the remainder of the + buffer past the given start position. + + If beginPos+howMany would extend past the end of the buffer, the + 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. + + 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 + 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. + + This function does not modify the buffer's streaming-related + cursors. + + If the buffer is modified in a separate thread while this + operation is running, results are undefined and will likely + result in corruption of the bound data or a segmentation fault. + + Design note: this function should arguably take a java.nio.Buffer + instead of ByteBuffer, but it can only operate on "direct" + buffers and the only such class offered by Java is (apparently) + ByteBuffer. + + @see https://docs.oracle.com/javase/8/docs/api/java/nio/Buffer.html + */ + public static native int sqlite3_bind_nio_buffer( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data, + int beginPos, int howMany + ); + + /** + Convenience overload which binds the given buffer's entire contents. + */ + public static int sqlite3_bind_nio_buffer( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data + ){ + return sqlite3_bind_nio_buffer(stmt, ndx, data, 0, -1); + } + /** Binds the given object at the given index. If o is null then this behaves like sqlite3_bind_null(). diff --git a/ext/jni/src/org/sqlite/jni/capi/Tester1.java b/ext/jni/src/org/sqlite/jni/capi/Tester1.java index 3ac58c67d3..0587eb6318 100644 --- a/ext/jni/src/org/sqlite/jni/capi/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/capi/Tester1.java @@ -38,6 +38,14 @@ import java.util.concurrent.Future; @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @interface SingleThreadOnly{} +/** + Annotation for Tester1 tests which must only be run if JNI-level support for + java.nio.Buffer is available. +*/ +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) +@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) +@interface RequiresNioBuffer{} + public class Tester1 implements Runnable { //! True when running in multi-threaded mode. private static boolean mtMode = false; @@ -557,6 +565,45 @@ public class Tester1 implements Runnable { sqlite3_close_v2(db); } + @RequiresNioBuffer + private void testBindByteBuffer(){ + 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); + 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); + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + int n = 0; + 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; + } + 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. */ + sqlite3_close_v2(db); + } + private void testSql(){ sqlite3 db = createNewDb(); sqlite3_stmt stmt = prepare(db, "SELECT 1"); @@ -1877,18 +1924,19 @@ public class Tester1 implements Runnable { if( forceFail ){ testMethods.add(m); } + }else if( m.isAnnotationPresent( RequiresNioBuffer.class ) + && !sqlite3_jni_supports_nio() ){ + outln("Skipping test for lack JNI nio.Buffer support: ",name,"()\n"); + ++nSkipped; }else if( !m.isAnnotationPresent( ManualTest.class ) ){ if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){ - if( 0==nSkipped++ ){ - out("Skipping tests in multi-thread mode:"); - } - out(" "+name+"()"); + out("Skipping test in multi-thread mode: ",name,"()\n"); + ++nSkipped; }else if( name.startsWith("test") ){ testMethods.add(m); } } } - if( nSkipped>0 ) out("\n"); } final long timeStart = System.currentTimeMillis(); diff --git a/manifest b/manifest index a6ae666a58..47b34117a7 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C JNI\swrapper1:\swhen\schecking\sfor\san\sout-of-bounds\sstatement\scolumn\sindex,\sperform\sthe\sis-statement-finalized\scheck\sbefore\sthe\srange\scheck\sso\sthat\sthe\sformer\sexception\strumps\sthe\slatter. -D 2023-11-11T14:50:01.933 +C JNI:\sadd\ssqlite3_bind_nio_buffer()\sand\sinitial\stests\sfor\sbinding\sByteBuffer\sobjects\sas\sblobs\son\sJVMs\swhich\shave\sJNI\ssupport\sfor\snio\sbuffers. +D 2023-11-13T14:58:37.421 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -241,8 +241,8 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile f2f3a31923293659b95225e932a286af1f2287d75bf88ad6c0fd1b9d9cd020d4 F ext/jni/README.md ef9ac115e97704ea995d743b4a8334e23c659e5534c3b64065a5405256d5f2f4 F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa -F ext/jni/src/c/sqlite3-jni.c 3774703e5865e7ff776b762de5386af8aa703e569bbb3a85c423c3f8473a3c26 -F ext/jni/src/c/sqlite3-jni.h 891444578550a7aa69fe5e0dedb3e6dedad752501ba99801f17797be51796934 +F ext/jni/src/c/sqlite3-jni.c a04d0d77e7391a69f7f8ca4b38e24de59b3a8f61610f2e91698c190c07283850 +F ext/jni/src/c/sqlite3-jni.h 2848299f845d36b4b6123d360e7a4eb960d040637a10158079af49f4ded16453 F ext/jni/src/org/sqlite/jni/annotation/NotNull.java 02091a8112e33389f1c160f506cd413168c8dfacbeda608a4946c6e3557b7d5a F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 0b1879852707f752512d4db9d7edd0d8db2f0c2612316ce1c832715e012ff6ba F ext/jni/src/org/sqlite/jni/annotation/package-info.java 977b374aed9d5853cbf3438ba3b0940abfa2ea4574f702a2448ee143b98ac3ca @@ -251,7 +251,7 @@ F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 0b72cdff61533b564d65b63 F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java c045a5b47e02bb5f1af91973814a905f12048c428a3504fbc5266d1c1be3de5a F ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 74cc4998a73d6563542ecb90804a3c4f4e828cb4bd69e61226d1a51f4646e759 F ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 7b8e19810c42b0ad21a04b5d8c804b32ee5905d137148703f16a75b612c380ca -F ext/jni/src/org/sqlite/jni/capi/CApi.java bd4a6490548f913bf9719443dee3d8a233f920ed1614b622738527d746e00f5d +F ext/jni/src/org/sqlite/jni/capi/CApi.java 5ef54290c17dca46d7f24001ac3b689559e1b37ee40d06b88fa5315d64863789 F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 57e2d275dcebe690b1fc1f3d34eb96879b2d7039bce30b563aee547bf45d8a8b F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 5bfa226a8e7a92e804fd52d6e42b4c7b875fa7a94f8e2c330af8cc244a8920ab @@ -269,7 +269,7 @@ F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 0d1e9afc9ff8a2adb94a155b72385 F ext/jni/src/org/sqlite/jni/capi/SQLTester.java 09bee15aa0eedac68d767ae21d9a6a62a31ade59182a3ccbf036d6463d9e30b1 F ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 93b9700fca4c68075ccab12fe0fbbc76c91cafc9f368e835b9bd7cd7732c8615 F ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java addf120e0e76e5be1ff2260daa7ce305ff9b5fafd64153a7a28e9d8f000a815f -F ext/jni/src/org/sqlite/jni/capi/Tester1.java b1a0c015d92a8d0c07a8f6751e9b057557cec9d803e002d48ee5f3b9963abd55 +F ext/jni/src/org/sqlite/jni/capi/Tester1.java 4ec21172917f641787767443f418854329bf9b9779807b644e000dac1ec77013 F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723 F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java c8bdf7848e6599115d601bcc9427ff902cb33129b9be32870ac6808e04b6ae56 F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 22d365746a78c5cd7ae10c39444eb7bbf1a819aad4bb7eb77b1edc47773a3950 @@ -2139,8 +2139,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P a6ab88e9a67f23ab7885402776282b94033cb48dbe34d4d18356e4dc22aae7cd -R 78a63cfa87011ebb24c6cac260db14c9 +P 0832f9a8e9f574b157c791c5cddc73aff7b2ff403509f5d78f310494d4a7f93d +R 7292f27854539ba720bc8a8fcd0c273f U stephan -Z ad92dad6ace48bd20f7cf67cbd6f4f40 +Z 1881d5bf6b59348a7807616e897efc0f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 4865106033..693d450f8a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0832f9a8e9f574b157c791c5cddc73aff7b2ff403509f5d78f310494d4a7f93d \ No newline at end of file +b10ce1ef82d84726fbf6a8f624d6530f84fefb505f7868b4a0ea910fed7a877f \ No newline at end of file