]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add commit/rollback hook support to JNI wrapper1.
authorstephan <stephan@noemail.net>
Sun, 5 Nov 2023 00:48:43 +0000 (00:48 +0000)
committerstephan <stephan@noemail.net>
Sun, 5 Nov 2023 00:48:43 +0000 (00:48 +0000)
FossilOrigin-Name: ff3d44fe42528d96533d22c7807472df89bca18f1def23b018e2f407318143f8

ext/jni/src/c/sqlite3-jni.c
ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java
ext/jni/src/org/sqlite/jni/capi/Tester1.java
ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java
manifest
manifest.uuid

index bf4e73b7c3eea8a383bf085309292f609054d6a5..0d60e49d139f0aaca64e1655d51364ad0e0b4516 100644 (file)
@@ -2942,7 +2942,10 @@ static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){
       ? (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback)
       : (int)((*env)->CallVoidMethod(env, hook.jObj, hook.midCallback), 0);
     S3JniIfThrew{
-      rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR, "hook callback threw");
+      rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR,
+                              isCommit
+                              ? "Commit hook callback threw"
+                              : "Rollback hook callback threw");
     }
     S3JniHook_localundup(hook);
   }
index 24373bdf2b311082d85bbd2569074f3b662da144..e1e55c78d291703e86407c70354ecc9b925c945d 100644 (file)
@@ -19,7 +19,8 @@ package org.sqlite.jni.capi;
 public interface CommitHookCallback extends CallbackProxy {
   /**
      Works as documented for the C-level sqlite3_commit_hook()
-     callback.  Must not throw.
+     callback. If it throws, the exception is translated into
+     a db-level error.
   */
   int call();
 }
index 48ab0314664e5026e9aa541f7a4bda7b093e4b63..4a8ceb181280f0abd3149bb28f771edbc946e716 100644 (file)
@@ -1096,6 +1096,7 @@ public class Tester1 implements Runnable {
 
   private void testCommitHook(){
     final sqlite3 db = createNewDb();
+    sqlite3_extended_result_codes(db, true);
     final ValueHolder<Integer> counter = new ValueHolder<>(0);
     final ValueHolder<Integer> hookResult = new ValueHolder<>(0);
     final CommitHookCallback theHook = new CommitHookCallback(){
@@ -1138,7 +1139,7 @@ public class Tester1 implements Runnable {
     affirm( 5 == counter.value );
     hookResult.value = SQLITE_ERROR;
     int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;");
-    affirm( SQLITE_CONSTRAINT == rc );
+    affirm( SQLITE_CONSTRAINT_COMMITHOOK == rc );
     affirm( 6 == counter.value );
     sqlite3_close_v2(db);
   }
index ffbc695242c84d67720c02434498914de09ed66d..dcd475ea682b3175c5a77c55e920ebe0fdf611bd 100644 (file)
@@ -1253,4 +1253,105 @@ public final class Sqlite implements AutoCloseable  {
     }
     checkRc( CApi.sqlite3_busy_handler(thisDb(), bhc) );
   }
+
+  public interface CommitHook {
+    /**
+       Must behave as documented for the C-level sqlite3_commit_hook()
+       callback. If it throws, the exception is translated into
+       a db-level error.
+    */
+    int call();
+  }
+
+  /**
+     A level of indirection to permit setCommitHook() to have similar
+     semantics as the C API, returning the previous hook. The caveat
+     is that if the low-level API is used to install a hook, it will
+     have a different hook type than Sqlite.CommitHook so
+     setCommitHook() will return null instead of that object.
+  */
+  private static class CommitHookProxy
+    implements org.sqlite.jni.capi.CommitHookCallback {
+    final CommitHook commitHook;
+    CommitHookProxy(CommitHook ch){
+      this.commitHook = ch;
+    }
+    @Override public int call(){
+      return commitHook.call();
+    }
+  }
+
+  /**
+     Analog to sqlite3_commit_hook(). Returns the previous hook, if
+     any (else null). Throws if this db is closed.
+
+     Minor caveat: if a commit hook is set on this object's underlying
+     db handle using the lower-level SQLite API, this function may
+     return null when replacing it, despite there being a hook,
+     because it will have a different callback type. So long as the
+     handle is only manipulated via the high-level API, this caveat
+     does not apply.
+  */
+  CommitHook setCommitHook( CommitHook c ){
+    CommitHookProxy chp = null;
+    if( null!=c ){
+      chp = new CommitHookProxy(c);
+    }
+    final org.sqlite.jni.capi.CommitHookCallback rv =
+      CApi.sqlite3_commit_hook(thisDb(), chp);
+    return (rv instanceof CommitHookProxy)
+      ? ((CommitHookProxy)rv).commitHook
+      : null;
+  }
+
+
+  public interface RollbackHook {
+    /**
+       Must behave as documented for the C-level sqlite3_rollback_hook()
+       callback. If it throws, the exception is translated into
+       a db-level error.
+    */
+    void call();
+  }
+
+  /**
+     A level of indirection to permit setRollbackHook() to have similar
+     semantics as the C API, returning the previous hook. The caveat
+     is that if the low-level API is used to install a hook, it will
+     have a different hook type than Sqlite.RollbackHook so
+     setRollbackHook() will return null instead of that object.
+  */
+  private static class RollbackHookProxy
+    implements org.sqlite.jni.capi.RollbackHookCallback {
+    final RollbackHook rollbackHook;
+    RollbackHookProxy(RollbackHook ch){
+      this.rollbackHook = ch;
+    }
+    @Override public void call(){rollbackHook.call();}
+  }
+
+  /**
+     Analog to sqlite3_rollback_hook(). Returns the previous hook, if
+     any (else null). Throws if this db is closed.
+
+     Minor caveat: if a rollback hook is set on this object's underlying
+     db handle using the lower-level SQLite API, this function may
+     return null when replacing it, despite there being a hook,
+     because it will have a different callback type. So long as the
+     handle is only manipulated via the high-level API, this caveat
+     does not apply.
+  */
+  RollbackHook setRollbackHook( RollbackHook c ){
+    RollbackHookProxy chp = null;
+    if( null!=c ){
+      chp = new RollbackHookProxy(c);
+    }
+    final org.sqlite.jni.capi.RollbackHookCallback rv =
+      CApi.sqlite3_rollback_hook(thisDb(), chp);
+    return (rv instanceof RollbackHookProxy)
+      ? ((RollbackHookProxy)rv).rollbackHook
+      : null;
+  }
+
+
 }
index 3a00c0953f81f09f45a6ad12b5bdad57d132d196..56ea35ef1f40e3278b5c2dc928be01b7bd0d5c62 100644 (file)
@@ -737,6 +737,87 @@ public class Tester2 implements Runnable {
     }
   }
 
+  private void testCommitHook(){
+    final Sqlite db = openDb();
+    final ValueHolder<Integer> counter = new ValueHolder<>(0);
+    final ValueHolder<Integer> hookResult = new ValueHolder<>(0);
+    final Sqlite.CommitHook theHook = new Sqlite.CommitHook(){
+        @Override public int call(){
+          ++counter.value;
+          return hookResult.value;
+        }
+      };
+    Sqlite.CommitHook oldHook = db.setCommitHook(theHook);
+    affirm( null == oldHook );
+    execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+    affirm( 2 == counter.value );
+    execSql(db, "BEGIN; SELECT 1; SELECT 2; COMMIT;");
+    affirm( 2 == counter.value /* NOT invoked if no changes are made */ );
+    execSql(db, "BEGIN; update t set a='d' where a='c'; COMMIT;");
+    affirm( 3 == counter.value );
+    oldHook = db.setCommitHook(theHook);
+    affirm( theHook == oldHook );
+    execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;");
+    affirm( 4 == counter.value );
+    oldHook = db.setCommitHook(null);
+    affirm( theHook == oldHook );
+    execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;");
+    affirm( 4 == counter.value );
+    oldHook = db.setCommitHook(null);
+    affirm( null == oldHook );
+    execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;");
+    affirm( 4 == counter.value );
+
+    final Sqlite.CommitHook newHook = new Sqlite.CommitHook(){
+        @Override public int call(){return 0;}
+      };
+    oldHook = db.setCommitHook(newHook);
+    affirm( null == oldHook );
+    execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;");
+    affirm( 4 == counter.value );
+    oldHook = db.setCommitHook(theHook);
+    affirm( newHook == oldHook );
+    execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;");
+    affirm( 5 == counter.value );
+    hookResult.value = CApi.SQLITE_ERROR;
+    int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;");
+    affirm( CApi.SQLITE_CONSTRAINT_COMMITHOOK == rc );
+    affirm( 6 == counter.value );
+    db.close();
+  }
+
+  private void testRollbackHook(){
+    final Sqlite db = openDb();
+    final ValueHolder<Integer> counter = new ValueHolder<>(0);
+    final Sqlite.RollbackHook theHook = new Sqlite.RollbackHook(){
+        @Override public void call(){
+          ++counter.value;
+        }
+      };
+    Sqlite.RollbackHook oldHook = db.setRollbackHook(theHook);
+    affirm( null == oldHook );
+    execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+    affirm( 0 == counter.value );
+    execSql(db, false, "BEGIN; SELECT 1; SELECT 2; ROLLBACK;");
+    affirm( 1 == counter.value /* contra to commit hook, is invoked if no changes are made */ );
+
+    final Sqlite.RollbackHook newHook = new Sqlite.RollbackHook(){
+        @Override public void call(){}
+      };
+    oldHook = db.setRollbackHook(newHook);
+    affirm( theHook == oldHook );
+    execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+    affirm( 1 == counter.value );
+    oldHook = db.setRollbackHook(theHook);
+    affirm( newHook == oldHook );
+    execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+    affirm( 2 == counter.value );
+    int rc = execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+    affirm( 0 == rc );
+    affirm( 3 == counter.value );
+    db.close();
+  }
+
   private void runTests(boolean fromThread) throws Exception {
     List<java.lang.reflect.Method> mlist = testMethods;
     affirm( null!=mlist );
index 0cdcd1cfcdd006ea5f63ed8e842b2f3d8317c598..68dcbace48d300e10cfbf3445f7e71f7f9caa0c2 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\sbusy-handler\ssupport\sto\sJNI\swrapper1.
-D 2023-11-05T00:02:47.384
+C Add\scommit/rollback\shook\ssupport\sto\sJNI\swrapper1.
+D 2023-11-05T00:48:43.424
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -241,7 +241,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3
 F ext/jni/GNUmakefile 36919b7c4fb8447da4330df9996c7b064b766957f8b7be214a30eab55a8b8072
 F ext/jni/README.md ef9ac115e97704ea995d743b4a8334e23c659e5534c3b64065a5405256d5f2f4
 F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
-F ext/jni/src/c/sqlite3-jni.c 931a7320f5b5745034b4fd61027ea7cc29559856e6da613e4fdcf01ef102e710
+F ext/jni/src/c/sqlite3-jni.c e24804e86759c0680064aacc46ab901d9b2b1a44eba312bcc9a387f15f044d12
 F ext/jni/src/c/sqlite3-jni.h 1c45fd4689cec42f3d84d2fee41bb494016a12fcb5fd80291095590666a14015
 F ext/jni/src/org/sqlite/jni/annotation/NotNull.java a99341e88154e70447596b1af6a27c586317df41a7e0f246fd41370cd7b723b2
 F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 0b1879852707f752512d4db9d7edd0d8db2f0c2612316ce1c832715e012ff6ba
@@ -255,7 +255,7 @@ F ext/jni/src/org/sqlite/jni/capi/CApi.java 4043d709626079cce6d524ef49122b934c04
 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
-F ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 29c002f3c638cc80f7db1594564a262d1beb32637824c3dca2d60a224d1f71d7
+F ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 482f53dfec9e3ac2a9070d3fceebd56250932aaaf7c4f5bc8de29fc011416e0c
 F ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java b995ca412f59b631803b93aa5b3684fce62e335d1e123207084c054abfd488d4
 F ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java 701f2e4d8bdeb27cfbeeb56315d15b13d8752b0fdbca705f31bd4366c58d8a33
 F ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java b7036dcb1ef1b39f1f36ac605dde0ff1a24a9a01ade6aa1a605039443e089a61
@@ -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 4bb5e62907a422a80a0fccbcb83085e9163c2c245451312a62c7550a45d16683
+F ext/jni/src/org/sqlite/jni/capi/Tester1.java 909ebd23111762c878116ebacf73a848c8323fb46e8128eb3d99a85d48905444
 F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723
 F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java 2766b8526bbffc4f1045f70e79f1bc1b1efe1c3e95ca06cdb8a7391032dda3b4
 F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 22d365746a78c5cd7ae10c39444eb7bbf1a819aad4bb7eb77b1edc47773a3950
@@ -296,9 +296,9 @@ F ext/jni/src/org/sqlite/jni/test-script-interpreter.md f9f25126127045d051e918fe
 F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java d5c108b02afd3c63c9e5e53f71f85273c1bfdc461ae526e0a0bb2b25e4df6483
 F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 43c43adfb7866098aadaaca1620028a6ec82d5193149970019b1cce9eb59fb03
 F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 2833afdb9af5c1949bb35f4c926a5351fba9d1cdf0996864caa7b47827a346c7
-F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java c930ca964f605ba8f175d3b0c85099d7f93069b59bf825929c9eef9e68ac96c5
+F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 782bb185ffc629cdabbd624565a52ed9b4b1b2d773b8b1d84476d91cbf94827d
 F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 929a1e2ab4e135fbbae7f0d2d609f77cfbbc60bbec7ba789ce23d9c73bc6156e
-F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 6f5fae3c3827ca42ef124c319b24907483aadda69b7453173f7807e0a94f33dd
+F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java ab1236dd65b4f90db729c88c71382b14aa179095f8b8e4b50835125bd0072f9e
 F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 7b89a7391f771692c5b83b0a5b86266abe8d59f1c77d7a0eccc9b79f259d79af
 F ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java c7d1452f9ff26175b3c19bbf273116cc2846610af68e01756d755f037fe7319f
 F ext/jni/src/tests/000-000-sanity.test c3427a0e0ac84d7cbe4c95fdc1cd4b61f9ddcf43443408f3000139478c4dc745
@@ -2142,8 +2142,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 0f673140681685ab390ecd7326a8b80d060b7ab23c31a2cfc28ba76fd5096afe
-R 2dabe33af17a981e8c8323ecd84b4487
+P dcf579ab2de4a3d3a437cde59b2fd60f1729c0bde31df1865117e6a5ea4bab20
+R f0002b14be80bf0bcecc7fa19a1e63af
 U stephan
-Z c2d53a448e9f555de7ff26068d4eba40
+Z 507dddf857173269602bfa9033ce1fa7
 # Remove this line to create a well-formed Fossil manifest.
index 97de8be89bcd14036ec170560f469495858cc9d1..ba526bfc7b61405fc85ef91106b79e2170f26a78 100644 (file)
@@ -1 +1 @@
-dcf579ab2de4a3d3a437cde59b2fd60f1729c0bde31df1865117e6a5ea4bab20
\ No newline at end of file
+ff3d44fe42528d96533d22c7807472df89bca18f1def23b018e2f407318143f8
\ No newline at end of file