From: stephan Date: Sun, 30 Jul 2023 07:44:03 +0000 (+0000) Subject: Bind sqlite3_update_hook() to JNI. X-Git-Tag: version-3.43.0~47^2~131 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fecad503a9bfe76218e9d88b20f301e37a95a20a;p=thirdparty%2Fsqlite.git Bind sqlite3_update_hook() to JNI. FossilOrigin-Name: a5bbaa9017839f8d8b92bfb44472d4c60fa3037bfae7846dc8350262c1332cde --- diff --git a/ext/jni/README.md b/ext/jni/README.md index 24296a7174..d62eddf6a7 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -91,6 +91,12 @@ conversion is performed. In short, conversion in Java, and there is no JNI C API for that conversion (JNI's `NewStringUTF()` returns MUTF-8). +Known consequences and limitations of this discrepancy include: + +- Database and table names must not contain characters which differ + in MUTF-8 and UTF-8, or certain APIs will mis-translate them on + their way between languages. + [modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 4daa1d104c..85d010b304 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -340,13 +340,13 @@ typedef struct { jmethodID jmidxCallback /* klazz's xCallback method */; } BusyHandlerJni; +/** State for various hook callbacks. */ typedef struct JniHookState JniHookState; struct JniHookState{ jobject jObj; jmethodID midCallback; }; - /** Per-(sqlite3*) state for bindings which do not have their own finalizer functions, e.g. tracing and commit/rollback hooks. This @@ -369,6 +369,7 @@ struct PerDbStateJni { JniHookState progress; JniHookState commitHook; JniHookState rollbackHook; + JniHookState updateHook; BusyHandlerJni busyHandler; }; @@ -1562,6 +1563,8 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv *env,jobject jDb, isCommit ? "xCommitHook" : "xRollbackHook", isCommit ? "()I" : "()V"); IFTHREW { + MARKER(("WARNING: callback MUST NOT THROW.\n")); + EXCEPTION_REPORT; EXCEPTION_CLEAR; s3jni_db_error(pDb, SQLITE_ERROR, "Cannot not find matching callback on " @@ -2098,6 +2101,82 @@ JDECL(jint,1trace_1v2)(JENV_JSELF,jobject jDb, jint traceMask, jobject jTracer){ return sqlite3_trace_v2(pDb, (unsigned)traceMask, s3jni_trace_impl, ps); } +static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, + const char *zTable, sqlite3_int64 nRowid){ + PerDbStateJni * const ps = pState; + JNIEnv * const env = ps->env; + /* ACHTUNG: this will break if zDb or zTable contain chars which are + different in MUTF-8 than UTF-8. That seems like a low risk, + but it's possible. */ + jstring jDbName; + jstring jTable; + jDbName = (*env)->NewStringUTF(env, zDb); + jTable = jDbName ? (*env)->NewStringUTF(env, zTable) : 0; + IFTHREW { + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + }else{ + (*env)->CallVoidMethod(env, ps->updateHook.jObj, + ps->updateHook.midCallback, + (jint)opId, jDbName, jTable, (jlong)nRowid); + IFTHREW{ + MARKER(("WARNING: callback MUST NOT THROW.\n")); + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + s3jni_db_error(ps->pDb, SQLITE_ERROR, "update hook callback threw."); + } + } + UNREF_L(jDbName); + UNREF_L(jTable); +} + + +JDECL(jobject,1update_1hook)(JENV_JSELF, jobject jDb, jobject jHook){ + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + PerDbStateJni * const ps = PerDbStateJni_for_db(env, pDb, 1); + jclass klazz; + jobject pOld = 0; + jmethodID xCallback; + JniHookState * const pHook = &ps->updateHook; + if(!ps){ + s3jni_db_error(pDb, SQLITE_NOMEM, 0); + return 0; + } + pOld = pHook->jObj; + if(pOld && jHook && + (*env)->IsSameObject(env, pOld, jHook)){ + return pOld; + } + if( !jHook ){ + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + memset(pHook, 0, sizeof(JniHookState)); + sqlite3_update_hook(pDb, 0, 0); + return pOld; + } + klazz = (*env)->GetObjectClass(env, jHook); + xCallback = (*env)->GetMethodID(env, klazz, "xUpdateHook", + "(ILjava/lang/String;Ljava/lang/String;J)V"); + IFTHREW { + EXCEPTION_CLEAR; + s3jni_db_error(pDb, SQLITE_ERROR, + "Cannot not find matching callback on " + "update hook object."); + }else{ + pHook->midCallback = xCallback; + pHook->jObj = REF_G(jHook); + sqlite3_update_hook(pDb, s3jni_update_hook_impl, ps); + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + } + return pOld; +} + JDECL(jbyteArray,1value_1blob)(JENV_JSELF, jobject jpSVal){ sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index f9fa4a92b1..0df788eb70 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1419,6 +1419,14 @@ JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1total_1changes64 JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1trace_1v2 (JNIEnv *, jclass, jobject, jint, jobject); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_update_hook + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/UpdateHook;)Lorg/sqlite/jni/UpdateHook; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1update_1hook + (JNIEnv *, jclass, jobject, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_value_blob diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 36b824a808..3e9fcab17f 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -661,7 +661,7 @@ public final class SQLite3Jni { public static native int sqlite3_trace_v2(@NotNull sqlite3 db, int traceMask, @Nullable Tracer tracer); - //TODO void *sqlite3_update_hook(sqlite3*, void(*)(void *,int ,char const *,char const *,sqlite3_int64), void*); + public static native UpdateHook sqlite3_update_hook(sqlite3 db, UpdateHook hook); public static native byte[] sqlite3_value_blob(@NotNull sqlite3_value v); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 67e3bf639a..f85e37a7b3 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -800,6 +800,56 @@ public class Tester1 { sqlite3_close_v2(db); } + private static void testUpdateHook(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder expectedOp = new ValueHolder<>(0); + final UpdateHook theHook = new UpdateHook(){ + @SuppressWarnings("unchecked") + public void xUpdateHook(int opId, String dbName, String tableName, long rowId){ + ++counter.value; + if( 0!=expectedOp.value ){ + affirm( expectedOp.value == opId ); + } + } + }; + UpdateHook oldHook = sqlite3_update_hook(db, theHook); + affirm( null == oldHook ); + expectedOp.value = SQLITE_INSERT; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 3 == counter.value ); + expectedOp.value = SQLITE_UPDATE; + execSql(db, "update t set a='d' where a='c';"); + affirm( 4 == counter.value ); + oldHook = sqlite3_update_hook(db, theHook); + affirm( theHook == oldHook ); + expectedOp.value = SQLITE_DELETE; + execSql(db, "DELETE FROM t where a='d'"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, null); + affirm( theHook == oldHook ); + execSql(db, "update t set a='e' where a='b';"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, null); + affirm( null == oldHook ); + + final UpdateHook newHook = new UpdateHook(){ + public void xUpdateHook(int opId, String dbName, String tableName, long rowId){ + } + }; + oldHook = sqlite3_update_hook(db, newHook); + affirm( null == oldHook ); + execSql(db, "update t set a='h' where a='a'"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, theHook); + affirm( newHook == oldHook ); + expectedOp.value = SQLITE_UPDATE; + execSql(db, "update t set a='i' where a='h'"); + affirm( 6 == counter.value ); + sqlite3_close_v2(db); + } + + private static void testRollbackHook(){ final sqlite3 db = createNewDb(); final ValueHolder counter = new ValueHolder<>(0); @@ -863,6 +913,7 @@ public class Tester1 { testProgress(); testCommitHook(); testRollbackHook(); + testUpdateHook(); //testSleep(); if(liArgs.indexOf("-v")>0){ listBoundMethods(); diff --git a/ext/jni/src/org/sqlite/jni/UpdateHook.java b/ext/jni/src/org/sqlite/jni/UpdateHook.java new file mode 100644 index 0000000000..171e2bdb41 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/UpdateHook.java @@ -0,0 +1,25 @@ +/* +** 2023-07-22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_update_hook(). +*/ +public interface UpdateHook { + /** + Works as documented for the sqlite3_update_hook() callback. + Must not throw. + */ + void xUpdateHook(int opId, String dbName, String tableName, long rowId); +} diff --git a/manifest b/manifest index d9877eabbf..3950de8156 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_rollback_hook()\sto\sJNI. -D 2023-07-30T06:44:21.370 +C Bind\ssqlite3_update_hook()\sto\sJNI. +D 2023-07-30T07:44:03.881 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,9 +231,9 @@ F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f4 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d -F ext/jni/README.md b62f1f0e67a6295e9a0283d4dffad6ed30ec50352aa36b3bd323a26593606c0f -F ext/jni/src/c/sqlite3-jni.c 84c082bd07d130b3db4121c32429a9ea1e57c109366b78909b3a7bba4b9afd68 -F ext/jni/src/c/sqlite3-jni.h 58f1f1a363184348c56dd6af47a2311640dde61ab0d9f4ba51fadf6ae5ba88c3 +F ext/jni/README.md ffbf87660efb7428d2b8aa644da1ddb4a3f4ac414936a9a44ce34a3899e12520 +F ext/jni/src/c/sqlite3-jni.c 77466a0b09141349c8e751edda689592865d28e4641ca3b446ab0ac456ae091e +F ext/jni/src/c/sqlite3-jni.h 85345dd3c970b539f1de4e6ad59c245fa6e80ca775a498ab1ed3d67f8615ce34 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a @@ -242,9 +242,10 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java 08a752b58a33696c5eaf0eb9361a0966 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 1e0ec8a4a5f95b0f595666f9b92246f3394f31f78f62503ed1d1ab879a695101 -F ext/jni/src/org/sqlite/jni/Tester1.java 7d65095d4d4e683f937de57f9340e7eda966a9bd0adba945b86b76570a12c3a7 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 50577e5d727ca3f53b2991988a2a0203b9ead199af60814b89f34edf98aa8d5a +F ext/jni/src/org/sqlite/jni/Tester1.java 2d43b851db4189e54527e7fb4d50493c8efaa6c0781d0f5cc7f249c95b48ce3b F ext/jni/src/org/sqlite/jni/Tracer.java c2fe1eba4a76581b93b375a7b95ab1919e5ae60accfb06d6beb067b033e9bae1 +F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee F ext/jni/src/org/sqlite/jni/sqlite3.java c7d0500c7269882243aafb41425928d094b2fcbdbc2fd1caffc276871cd3fae3 F ext/jni/src/org/sqlite/jni/sqlite3_context.java 841ac0384ec23e7d24ad9a928f8728b98bd3c4c3814d401200c6531786b9c241 @@ -2069,8 +2070,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 be4459b495cc2555e4d8ca24dd56c3da1036d25af7cc077bbc3d93c2a3e8c40b -R 6da75ca1ede9a6d1b13c3955b22a3d72 +P 5f8ee44098d74ac2b98e4dd43ad80d3b919528358b3f992b425af7fa6262dcee +R a218970a738fde3116e0f53364fc20ff U stephan -Z 04a18495036c22c78ae28b1e24549558 +Z eb57a2fe6bbc5fb050f3fa2299b80262 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 5d2b1e0838..2872ca44b1 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5f8ee44098d74ac2b98e4dd43ad80d3b919528358b3f992b425af7fa6262dcee \ No newline at end of file +a5bbaa9017839f8d8b92bfb44472d4c60fa3037bfae7846dc8350262c1332cde \ No newline at end of file