From bdfc51dfef8b5186d552ed6afb7a3a68dd5fb063 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 14 Nov 2023 04:59:57 +0000 Subject: [PATCH] JNI: add sqlite3_blob_read_nio_buffer() and iron out the blob/ByteBuffer interface somewhat. FossilOrigin-Name: 7df317b448a09ae77e2c68cc901fdb6d56a2246c1313f06bebd1f3e53f02c19b --- ext/jni/src/c/sqlite3-jni.c | 78 ++++++--- ext/jni/src/c/sqlite3-jni.h | 8 + ext/jni/src/org/sqlite/jni/capi/CApi.java | 175 ++++++++++++++++--- ext/jni/src/org/sqlite/jni/capi/Tester1.java | 51 +++++- manifest | 18 +- manifest.uuid | 2 +- 6 files changed, 275 insertions(+), 57 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index dcf2cdb4cd..6668f0b728 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -2485,7 +2485,7 @@ struct S3JniNioArgs { 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/read/write */ + 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; @@ -2494,32 +2494,39 @@ static const S3JniNioArgs S3JniNioArgs_empty = { }; /* -** 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. +** Internal helper for sqlite3_bind_nio_buffer(), +** sqlite3_result_nio_buffer(), and similar methods which take a +** ByteBuffer object as either input or output. 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, +** The (jBuffer, iOffset, iHowMany) arguments are the (ByteBuffer, offset, ** length) arguments to the bind/result method. ** +** If iHowMany is negative then it's treated as "until the end" and +** the calculated slice is trimmed to fit if needed. If iHowMany is +** positive and extends past the end of jBuffer then SQLITE_ERROR is +** returned. +** ** 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 iOffset, jint iN + jobject jBuffer, jint iOffset, jint iHowMany ){ jlong iEnd = 0; + const int bAllowTruncate = iHowMany<0; *pArgs = S3JniNioArgs_empty; pArgs->jBuf = jBuffer; pArgs->iOffset = iOffset; - pArgs->iHowMany = iN; + pArgs->iHowMany = iHowMany; assert( SJG.g.cByteBuffer ); if( pArgs->iOffset<0 ){ return SQLITE_ERROR - /* SQLITE_MISUSE would arguably fit better but we use + /* SQLITE_MISUSE or SQLITE_RANGE would fit better but we use SQLITE_ERROR for consistency with the code documented for a negative target blob offset in sqlite3_blob_read/write(). */; } @@ -2536,7 +2543,15 @@ static int s3jni_setup_nio_args( iEnd = pArgs->iHowMany<0 ? pArgs->nBuf - pArgs->iOffset : pArgs->iOffset + pArgs->iHowMany; - if( iEnd>(jlong)pArgs->nBuf ) iEnd = pArgs->nBuf - pArgs->iOffset; + if( iEnd>(jlong)pArgs->nBuf ){ + if( bAllowTruncate ){ + iEnd = pArgs->nBuf - pArgs->iOffset; + }else{ + return SQLITE_ERROR + /* again: for consistency with blob_read/write(), though + SQLITE_MISUSE or SQLITE_RANGE would be a better fit. */; + } + } if( iEnd - pArgs->iOffset > (jlong)SQLITE_MAX_LENGTH ){ return SQLITE_TOOBIG; } @@ -2545,6 +2560,7 @@ static int s3jni_setup_nio_args( pArgs->pStart = pArgs->p + pArgs->iOffset; pArgs->nOut = (int)(iEnd - pArgs->iOffset); assert( pArgs->nOut > 0 ); + assert( (pArgs->pStart + pArgs->nOut) <= (pArgs->p + pArgs->nBuf) ); return 0; } @@ -2562,10 +2578,6 @@ S3JniApi(sqlite3_bind_nio_buffer(),jint,1bind_1nio_1buffer)( }else if( !args.pStart || !args.nOut ){ return sqlite3_bind_null(pStmt, ndx); } - assert( args.nOut>0 ); - assert( args.nBuf > 0 ); - assert( args.pStart != 0 ); - assert( (args.pStart + args.nOut) <= (args.p + args.nBuf) ); return sqlite3_bind_blob( pStmt, (int)ndx, args.pStart, args.nOut, SQLITE_TRANSIENT ); } @@ -2784,6 +2796,30 @@ S3JniApi(sqlite3_blob_read(),jint,1blob_1read)( return rc; } +S3JniApi(sqlite3_blob_read_nio_buffer(),jint,1blob_1read_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 || iHowMany<0 ){ + return SQLITE_MISUSE; + }else if( iTgtOff<0 || iSrcOff<0 ){ + return SQLITE_ERROR + /* for consistency with underlying sqlite3_blob_read() */; + }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.iHowMany>0 ); + return sqlite3_blob_read( b, args.pStart, (int)args.nOut, (int)iSrcOff ); +} + S3JniApi(sqlite3_blob_reopen(),jint,1blob_1reopen)( JniArgsEnvClass, jlong jpBlob, jlong iNewRowId ){ @@ -2806,7 +2842,7 @@ S3JniApi(sqlite3_blob_write(),jint,1blob_1write)( } S3JniApi(sqlite3_blob_write_nio_buffer(),jint,1blob_1write_1nio_1buffer)( - JniArgsEnvClass, jlong jpBlob, jint iSrcOff, jobject jBB, jint iTgtOff, jint iHowMany + JniArgsEnvClass, jlong jpBlob, jint iTgtOff, jobject jBB, jint iSrcOff, jint iHowMany ){ sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob); S3JniNioArgs args; @@ -2819,17 +2855,13 @@ S3JniApi(sqlite3_blob_write_nio_buffer(),jint,1blob_1write_1nio_1buffer)( }else if( 0==iHowMany ){ return 0; } - rc = s3jni_setup_nio_args(env, &args, jBB, iTgtOff, iHowMany); + rc = s3jni_setup_nio_args(env, &args, jBB, iSrcOff, 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 ); + return sqlite3_blob_write( b, args.pStart, (int)args.nOut, (int)iTgtOff ); } /* Central C-to-Java busy handler proxy. */ diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index c3a5d178b2..20f42af46a 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1001,6 +1001,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1open JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read (JNIEnv *, jclass, jlong, jbyteArray, jint); +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_blob_read_nio_buffer + * Signature: (JILjava/nio/ByteBuffer;II)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read_1nio_1buffer + (JNIEnv *, jclass, jlong, jint, jobject, jint, jint); + /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_blob_reopen diff --git a/ext/jni/src/org/sqlite/jni/capi/CApi.java b/ext/jni/src/org/sqlite/jni/capi/CApi.java index 36f101c3e5..090adf94f4 100644 --- a/ext/jni/src/org/sqlite/jni/capi/CApi.java +++ b/ext/jni/src/org/sqlite/jni/capi/CApi.java @@ -312,8 +312,8 @@ public final class CApi { 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 beginPos+howMany would extend past the end of the buffer then + SQLITE_ERROR is returned. If any of the following are true, this function behaves like sqlite3_bind_null(): the buffer is null, beginPos is at or past @@ -559,13 +559,116 @@ public final class CApi { }; private static native int sqlite3_blob_read( - @NotNull long ptrToBlob, @NotNull byte[] target, int iOffset + @NotNull long ptrToBlob, @NotNull byte[] target, int srcOffset ); + /** + As per C's sqlite3_blob_read(), but writes its output to the + given byte array. Note that the final argument is the offset of + the source buffer, not the target array. + */ public static int sqlite3_blob_read( - @NotNull sqlite3_blob b, @NotNull byte[] target, int iOffset + @NotNull sqlite3_blob src, @NotNull byte[] target, int srcOffset ){ - return sqlite3_blob_read(b.getNativePointer(), target, iOffset); + return sqlite3_blob_read(src.getNativePointer(), target, srcOffset); + } + + /** + An internal level of indirection. + */ + private static native int sqlite3_blob_read_nio_buffer( + @NotNull long ptrToBlob, int srcOffset, + @NotNull java.nio.ByteBuffer tgt, int tgtOffset, int howMany + ); + + /** + Reads howMany bytes from offset srcOffset of src into position + tgtOffset of tgt. + + Returns SQLITE_MISUSE if src is null, tgt is null, or + sqlite3_jni_supports_nio() returns false. Returns SQLITE_ERROR if + howMany or either offset are negative. If argument validation + succeeds, it returns the result of the underlying call to + sqlite3_blob_read() (0 on success). + */ + public static int sqlite3_blob_read_nio_buffer( + @NotNull sqlite3_blob src, int srcOffset, + @NotNull java.nio.ByteBuffer tgt, int tgtOffset, int howMany + ){ + return (JNI_SUPPORTS_NIO && src!=null && tgt!=null) + ? sqlite3_blob_read_nio_buffer( + src.getNativePointer(), srcOffset, tgt, tgtOffset, howMany + ) + : SQLITE_MISUSE; + } + + /** + Convenience overload which reads howMany bytes from position + srcOffset of src and returns the result as a new ByteBuffer. + + srcOffset may not be negative. If howMany is negative, it is + treated as all bytes following srcOffset. + + Returns null if sqlite3_jni_supports_nio(), any arguments are + invalid, if the number of bytes to read is 0 or is larger than + the src blob, or the underlying call to sqlite3_blob_read() fails + for any reason. + */ + public static java.nio.ByteBuffer sqlite3_blob_read_nio_buffer( + @NotNull sqlite3_blob src, int srcOffset, int howMany + ){ + if( !JNI_SUPPORTS_NIO || src==null ) return null; + else if( srcOffset<0 ) return null; + final int nB = sqlite3_blob_bytes(src); + if( srcOffset>=nB ) return null; + else if( howMany<0 ) howMany = nB - srcOffset; + if( srcOffset + howMany > nB ) return null; + final java.nio.ByteBuffer tgt = + java.nio.ByteBuffer.allocateDirect(howMany); + final int rc = sqlite3_blob_read_nio_buffer( + src.getNativePointer(), srcOffset, tgt, 0, howMany + ); + return 0==rc ? tgt : null; + } + + /** + Overload alias for sqlite3_blob_read_nio_buffer(). + */ + public static int sqlite3_blob_read( + @NotNull sqlite3_blob src, int srcOffset, + @NotNull java.nio.ByteBuffer tgt, + int tgtOffset, int howMany + ){ + return sqlite3_blob_read_nio_buffer( + src, srcOffset, tgt, tgtOffset, howMany + ); + } + + /** + Convenience overload which uses 0 for both src and tgt offsets + and reads a number of bytes equal to the smaller of + sqlite3_blob_bytes(src) and tgt.limit(). + + On success it sets tgt.limit() to the number of bytes read. On + error, tgt.limit() is not modified. + + Returns 0 on success. Returns SQLITE_MISUSE is either argument is + null or sqlite3_jni_supports_nio() returns false. Else it returns + the result of the underlying call to sqlite3_blob_read(). + */ + public static int sqlite3_blob_read( + @NotNull sqlite3_blob src, + @NotNull java.nio.ByteBuffer tgt + ){ + if(!JNI_SUPPORTS_NIO || src==null || tgt==null) return SQLITE_MISUSE; + final int nSrc = sqlite3_blob_bytes(src); + final int nTgt = tgt.limit(); + final int nRead = nTgt3 ){ + affirm( br[i] == bbr2.get(i-4) ); + } + if( i < bbr4.limit() ){ + affirm( br[i] == bbr4.get(i) ); + } } sqlite3_close_v2(db); } diff --git a/manifest b/manifest index 040b8a2110..d62656917f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C JNI:\schange\ssqlite3_prepare_multi()'s\sexception-handling\ssemantics\sto\sbe\smore\sC-like\sand,\sto\ssupport\sthat,\sadd\sthe\spackage-private\ssqlite3_jni_db_error()\smethod\sto\sset\sthe\sdb\serror\sstate\sfrom\spackage-level\sJava\scode. -D 2023-11-14T02:43:30.475 +C JNI:\sadd\ssqlite3_blob_read_nio_buffer()\sand\siron\sout\sthe\sblob/ByteBuffer\sinterface\ssomewhat. +D 2023-11-14T04:59:57.233 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 7d14bf998e9862ecf01b121067b745eeb75668ca0a431df6e2a86fb5a500bca5 -F ext/jni/src/c/sqlite3-jni.h b8876cce091767e0655200b7653dc5a0ae117b53cff7b1090e9dbc2e69775ed5 +F ext/jni/src/c/sqlite3-jni.c 9828d7b6b584c55261e4dd65d86ce4da33daf6cee2966b191ac332ce47efac1c +F ext/jni/src/c/sqlite3-jni.h 0ed09051f16f612680603a297fefa2c131c4a7e98e0b41cdd9ece08428b47d48 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 e96ed33a5c235082a59bf73d861acbbb7b760f2c790ac719e495d8512e4de8c3 +F ext/jni/src/org/sqlite/jni/capi/CApi.java a7c53a3226c6826ada00752a651a31e6cce3b8d741a02b2d45cb0d1e3dfc3a80 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 fb0d859a07988bf0757b0910431d8d12a5f7f177eb7e7632f27eebc524b2aa92 +F ext/jni/src/org/sqlite/jni/capi/Tester1.java dcaa283a27aecb25dfd8f1a610885fb95d24945235b51ea13a1143585922de04 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 ca32af8542aa2725cc87f54541b19897556f610e4674edf9f22a84e3d4097a82 -R 8cb29fe765a47cc780c8d97ffc39bea6 +P 46656b354311ec0a36832af1c4ccb3b6a244aa55cfb3681e25c3f42b13b387dd +R 4e0b6f66f2c79b3b0f3d9c780b63f86e U stephan -Z 32cdbaea235e75a5f5809e209395feaf +Z 5994994aa13f973034ef1e21e15b70de # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f6caae534c..8b049893ca 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -46656b354311ec0a36832af1c4ccb3b6a244aa55cfb3681e25c3f42b13b387dd \ No newline at end of file +7df317b448a09ae77e2c68cc901fdb6d56a2246c1313f06bebd1f3e53f02c19b \ No newline at end of file -- 2.47.2