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
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
JniHookState progress;
JniHookState commitHook;
JniHookState rollbackHook;
+ JniHookState updateHook;
BusyHandlerJni busyHandler;
};
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 "
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);
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
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);
sqlite3_close_v2(db);
}
+ private static void testUpdateHook(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ final ValueHolder<Integer> 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<Integer> counter = new ValueHolder<>(0);
testProgress();
testCommitHook();
testRollbackHook();
+ testUpdateHook();
//testSleep();
if(liArgs.indexOf("-v")>0){
listBoundMethods();
--- /dev/null
+/*
+** 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);
+}
-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
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
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
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.
-5f8ee44098d74ac2b98e4dd43ad80d3b919528358b3f992b425af7fa6262dcee
\ No newline at end of file
+a5bbaa9017839f8d8b92bfb44472d4c60fa3037bfae7846dc8350262c1332cde
\ No newline at end of file