]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Bind sqlite3_preupdate_hook() and friends to JNI.
authorstephan <stephan@noemail.net>
Wed, 23 Aug 2023 13:17:37 +0000 (13:17 +0000)
committerstephan <stephan@noemail.net>
Wed, 23 Aug 2023 13:17:37 +0000 (13:17 +0000)
FossilOrigin-Name: d0c425b5c1d3aac5ead18a501a3760b4506d68d373cb3be484247042cf2fa8d4

ext/jni/GNUmakefile
ext/jni/src/c/sqlite3-jni.c
ext/jni/src/c/sqlite3-jni.h
ext/jni/src/org/sqlite/jni/OutputPointer.java
ext/jni/src/org/sqlite/jni/PreUpdateHook.java [new file with mode: 0644]
ext/jni/src/org/sqlite/jni/SQLite3Jni.java
ext/jni/src/org/sqlite/jni/Tester1.java
manifest
manifest.uuid

index 1d77fc5f749163d7e779cb66ec4f83bd331a7371..cc728003d78a39b180059c7833d8c682b230d069 100644 (file)
@@ -161,6 +161,7 @@ SQLITE_OPT = \
   -DSQLITE_ENABLE_DBSTAT_VTAB \
   -DSQLITE_ENABLE_BYTECODE_VTAB \
   -DSQLITE_ENABLE_OFFSET_SQL_FUNC \
+  -DSQLITE_ENABLE_PREUPDATE_HOOK \
   -DSQLITE_ENABLE_SQLLOG \
   -DSQLITE_OMIT_LOAD_EXTENSION \
   -DSQLITE_OMIT_DEPRECATED \
index 543f3be549fe2f566944c46f7892d87024a90dd0..93c5d5f1d2dbaf9e0969a4a6936bd833a989dc56 100644 (file)
@@ -239,6 +239,7 @@ static const struct {
   const S3NphRef OutputPointer_ByteArray;
   const S3NphRef OutputPointer_sqlite3;
   const S3NphRef OutputPointer_sqlite3_stmt;
+  const S3NphRef OutputPointer_sqlite3_value;
 #ifdef SQLITE_ENABLE_FTS5
   const S3NphRef Fts5Context;
   const S3NphRef Fts5ExtensionApi;
@@ -248,22 +249,23 @@ static const struct {
 #endif
 } S3NphRefs = {
 #define NREF(INDEX, JAVANAME) { INDEX, "org/sqlite/jni/" JAVANAME }
-  NREF(0, "sqlite3"),
-  NREF(1, "sqlite3_stmt"),
-  NREF(2, "sqlite3_context"),
-  NREF(3, "sqlite3_value"),
-  NREF(4, "OutputPointer$Int32"),
-  NREF(5, "OutputPointer$Int64"),
-  NREF(6, "OutputPointer$String"),
-  NREF(7, "OutputPointer$ByteArray"),
-  NREF(8, "OutputPointer$sqlite3"),
-  NREF(9, "OutputPointer$sqlite3_stmt"),
+  NREF(0,  "sqlite3"),
+  NREF(1,  "sqlite3_stmt"),
+  NREF(2,  "sqlite3_context"),
+  NREF(3,  "sqlite3_value"),
+  NREF(4,  "OutputPointer$Int32"),
+  NREF(5,  "OutputPointer$Int64"),
+  NREF(6,  "OutputPointer$String"),
+  NREF(7,  "OutputPointer$ByteArray"),
+  NREF(8,  "OutputPointer$sqlite3"),
+  NREF(9,  "OutputPointer$sqlite3_stmt"),
+  NREF(10, "OutputPointer$sqlite3_value"),
 #ifdef SQLITE_ENABLE_FTS5
-  NREF(10, "Fts5Context"),
-  NREF(11, "Fts5ExtensionApi"),
-  NREF(12, "fts5_api"),
-  NREF(13, "fts5_tokenizer"),
-  NREF(14, "Fts5Tokenizer")
+  NREF(11, "Fts5Context"),
+  NREF(12, "Fts5ExtensionApi"),
+  NREF(13, "fts5_api"),
+  NREF(14, "fts5_tokenizer"),
+  NREF(15, "Fts5Tokenizer")
 #endif
 #undef NREF
 };
@@ -394,15 +396,20 @@ struct S3JniDb {
                    receive. */;
   char * zMainDbName  /* Holds the string allocated on behalf of
                          SQLITE_DBCONFIG_MAINDBNAME. */;
-  S3JniHook busyHandler;
-  S3JniHook collation;
-  S3JniHook collationNeeded;
-  S3JniHook commitHook;
-  S3JniHook progress;
-  S3JniHook rollbackHook;
-  S3JniHook trace;
-  S3JniHook updateHook;
-  S3JniHook authHook;
+  struct {
+    S3JniHook busyHandler;
+    S3JniHook collation;
+    S3JniHook collationNeeded;
+    S3JniHook commit;
+    S3JniHook progress;
+    S3JniHook rollback;
+    S3JniHook trace;
+    S3JniHook update;
+    S3JniHook auth;
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+    S3JniHook preUpdate;
+#endif
+  } hooks;
 #ifdef SQLITE_ENABLE_FTS5
   jobject jFtsApi  /* global ref to s3jni_fts5_api_from_db() */;
 #endif
@@ -957,13 +964,16 @@ static void S3JniDb_set_aside(JNIEnv * env, S3JniDb * const s){
       SJG.perDb.aUsed = s->pNext;
     }
     sqlite3_free( s->zMainDbName );
-#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->MEMBER, XDESTROY)
+#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->hooks.MEMBER, XDESTROY)
     UNHOOK(trace, 0);
     UNHOOK(progress, 0);
-    UNHOOK(commitHook, 0);
-    UNHOOK(rollbackHook, 0);
-    UNHOOK(updateHook, 0);
-    UNHOOK(authHook, 0);
+    UNHOOK(commit, 0);
+    UNHOOK(rollback, 0);
+    UNHOOK(update, 0);
+    UNHOOK(auth, 0);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+    UNHOOK(preUpdate, 0);
+#endif
     UNHOOK(collation, 1);
     UNHOOK(collationNeeded, 1);
     UNHOOK(busyHandler, 1);
@@ -1308,7 +1318,7 @@ static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlon
 ** v.
 */
 static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut,
-                              jobject jDb){
+                                      jobject jDb){
   jfieldID const setter = setupOutputPointer(
     env, &S3NphRefs.OutputPointer_sqlite3, "Lorg/sqlite/jni/sqlite3;", jOut
   );
@@ -1321,15 +1331,31 @@ static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut,
 ** v.
 */
 static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOut,
-                                   jobject jStmt){
+                                           jobject jStmt){
   jfieldID const setter = setupOutputPointer(
     env, &S3NphRefs.OutputPointer_sqlite3_stmt,
-                     "Lorg/sqlite/jni/sqlite3_stmt;", jOut
+    "Lorg/sqlite/jni/sqlite3_stmt;", jOut
   );
   (*env)->SetObjectField(env, jOut, setter, jStmt);
   EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_stmt.value");
 }
 
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+/*
+** Sets the value property of the OutputPointer.sqlite3_value jOut object to
+** v.
+*/
+static void OutputPointer_set_sqlite3_value(JNIEnv * const env, jobject const jOut,
+                                            jobject jValue){
+  jfieldID const setter = setupOutputPointer(
+    env, &S3NphRefs.OutputPointer_sqlite3_value,
+    "Lorg/sqlite/jni/sqlite3_value;", jOut
+  );
+  (*env)->SetObjectField(env, jOut, setter, jValue);
+  EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_value.value");
+}
+#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
+
 #ifdef SQLITE_ENABLE_FTS5
 #if 0
 /*
@@ -1391,7 +1417,8 @@ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs,
   }
   (*env)->SetByteArrayRegion(env, jbaLhs, 0, (jint)nLhs, (const jbyte*)lhs);
   (*env)->SetByteArrayRegion(env, jbaRhs, 0, (jint)nRhs, (const jbyte*)rhs);
-  rc = (*env)->CallIntMethod(env, ps->collation.jObj, ps->collation.midCallback,
+  rc = (*env)->CallIntMethod(env, ps->hooks.collation.jObj,
+                             ps->hooks.collation.midCallback,
                              jbaLhs, jbaRhs);
   EXCEPTION_IGNORE;
   UNREF_L(jbaLhs);
@@ -1402,7 +1429,7 @@ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs,
 /* Collation finalizer for use by the sqlite3 internals. */
 static void CollationState_xDestroy(void *pArg){
   S3JniDb * const ps = pArg;
-  S3JniHook_unref( s3jni_get_env(), &ps->collation, 1 );
+  S3JniHook_unref( s3jni_get_env(), &ps->hooks.collation, 1 );
 }
 
 /*
@@ -1777,6 +1804,11 @@ WRAP_INT_DB(1error_1offset,            sqlite3_error_offset)
 WRAP_INT_DB(1extended_1errcode,        sqlite3_extended_errcode)
 WRAP_MUTF8_VOID(1libversion,           sqlite3_libversion)
 WRAP_INT_VOID(1libversion_1number,     sqlite3_libversion_number)
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+WRAP_INT_DB(1preupdate_1blobwrite,     sqlite3_preupdate_blobwrite)
+WRAP_INT_DB(1preupdate_1count,         sqlite3_preupdate_count)
+WRAP_INT_DB(1preupdate_1depth,         sqlite3_preupdate_depth)
+#endif
 WRAP_INT_INT(1sleep,                   sqlite3_sleep)
 WRAP_MUTF8_VOID(1sourceid,             sqlite3_sourceid)
 WRAP_INT_VOID(1threadsafe,             sqlite3_threadsafe)
@@ -1969,10 +2001,10 @@ JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt,
 static int s3jni_busy_handler(void* pState, int n){
   S3JniDb * const ps = (S3JniDb *)pState;
   int rc = 0;
-  if( ps->busyHandler.jObj ){
+  if( ps->hooks.busyHandler.jObj ){
     LocalJniGetEnv;
-    rc = (*env)->CallIntMethod(env, ps->busyHandler.jObj,
-                               ps->busyHandler.midCallback, (jint)n);
+    rc = (*env)->CallIntMethod(env, ps->hooks.busyHandler.jObj,
+                               ps->hooks.busyHandler.midCallback, (jint)n);
     IFTHREW{
       EXCEPTION_WARN_CALLBACK_THREW("sqlite3_busy_handler() callback");
       rc = s3jni_db_exception(env, ps, SQLITE_ERROR,
@@ -1987,7 +2019,7 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){
   int rc = 0;
   if(!ps) return (jint)SQLITE_NOMEM;
   if(jBusy){
-    S3JniHook * const pHook = &ps->busyHandler;
+    S3JniHook * const pHook = &ps->hooks.busyHandler;
     if(pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy)){
       /* Same object - this is a no-op. */
       return 0;
@@ -2004,7 +2036,7 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){
       return rc;
     }
   }else{
-    S3JniHook_unref(env, &ps->busyHandler, 1);
+    S3JniHook_unref(env, &ps->hooks.busyHandler, 1);
   }
   return jBusy
     ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps)
@@ -2014,7 +2046,7 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){
 JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){
   S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
   if( ps ){
-    S3JniHook_unref(env, &ps->busyHandler, 1);
+    S3JniHook_unref(env, &ps->hooks.busyHandler, 1);
     return sqlite3_busy_timeout(ps->pDb, (int)ms);
   }
   return SQLITE_MISUSE;
@@ -2092,8 +2124,8 @@ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb,
     s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
     EXCEPTION_CLEAR;
   }else{
-    (*env)->CallVoidMethod(env, ps->collationNeeded.jObj,
-                           ps->collationNeeded.midCallback,
+    (*env)->CallVoidMethod(env, ps->hooks.collationNeeded.jObj,
+                           ps->hooks.collationNeeded.midCallback,
                            ps->jDb, (jint)eTextRep, jName);
     IFTHREW{
       s3jni_db_exception(env, ps, 0, "sqlite3_collation_needed() callback threw");
@@ -2107,7 +2139,7 @@ JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){
   jclass klazz;
   jobject pOld = 0;
   jmethodID xCallback;
-  S3JniHook * const pHook = &ps->collationNeeded;
+  S3JniHook * const pHook = &ps->hooks.collationNeeded;
   int rc;
 
   if( !ps ) return SQLITE_MISUSE;
@@ -2192,10 +2224,10 @@ JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt,
 static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){
   LocalJniGetEnv;
   int rc = isCommit
-    ? (int)(*env)->CallIntMethod(env, ps->commitHook.jObj,
-                                 ps->commitHook.midCallback)
-    : (int)((*env)->CallVoidMethod(env, ps->rollbackHook.jObj,
-                                   ps->rollbackHook.midCallback), 0);
+    ? (int)(*env)->CallIntMethod(env, ps->hooks.commit.jObj,
+                                 ps->hooks.commit.midCallback)
+    : (int)((*env)->CallVoidMethod(env, ps->hooks.rollback.jObj,
+                                   ps->hooks.rollback.midCallback), 0);
   IFTHREW{
     EXCEPTION_CLEAR;
     rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "hook callback threw.");
@@ -2211,13 +2243,14 @@ static void s3jni_rollback_hook_impl(void *pP){
   (void)s3jni_commit_rollback_hook_impl(0, pP);
 }
 
-static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobject jDb,
-                                          jobject jHook){
+static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,
+                                          jobject jDb, jobject jHook){
   S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
   jclass klazz;
   jobject pOld = 0;
   jmethodID xCallback;
-  S3JniHook * const pHook = isCommit ? &ps->commitHook : &ps->rollbackHook;
+  S3JniHook * const pHook =
+    isCommit ? &ps->hooks.commit : &ps->hooks.rollback;
   if(!ps){
     s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
     return 0;
@@ -2367,7 +2400,7 @@ JDECL(jint,1config__Lorg_sqlite_jni_SQLLog_2)(JENV_CSELF, jobject jLog){
   return rc;
 #else
   MARKER(("Warning: built without SQLITE_ENABLE_SQLLOG.\n"));
-  return SQLITE_RANGE;
+  return SQLITE_MISUSE;
 #endif
 }
 
@@ -2380,13 +2413,13 @@ JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){
 JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb,
                                jstring name, jint eTextRep,
                                jobject oCollation){
-  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
-  jclass klazz;
   int rc;
   const char *zName;
-  S3JniHook * pHook;
-  if(!ps) return (jint)SQLITE_NOMEM;
-  pHook = &ps->collation;
+  jclass klazz;
+  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
+  S3JniHook * const pHook = ps ? &ps->hooks.collation : 0;
+
+  if( !pHook ) return SQLITE_MISUSE;
   klazz = (*env)->GetObjectClass(env, oCollation);
   pHook->midCallback = (*env)->GetMethodID(env, klazz, "xCompare",
                                            "([B[B)I");
@@ -2841,12 +2874,201 @@ JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jDb, jbyteArra
                                   prepFlags, jOutStmt, outTail);
 }
 
+/*
+** Impl for C-to-Java of the callbacks for both sqlite3_update_hook()
+** and sqlite3_preupdate_hook().  The differences are that for
+** update_hook():
+**
+** - pDb is NULL
+** - iKey1 is the row ID
+** - iKey2 is unused
+*/
+static void s3jni_updatepre_hook_impl(void * pState, sqlite3 *pDb, int opId,
+                                      const char *zDb, const char *zTable,
+                                      sqlite3_int64 iKey1, sqlite3_int64 iKey2){
+  S3JniDb * const ps = pState;
+  LocalJniGetEnv;
+  S3JniEnv * const jc = S3JniGlobal_env_cache(env);
+  jstring jDbName;
+  jstring jTable;
+  S3JniHook * pHook;
+  const int isPre = 0!=pDb;
+
+  pHook = isPre ?
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+    &ps->hooks.preUpdate
+#else
+    0
+#endif
+    : &ps->hooks.update;
+
+  assert( pHook );
+  jDbName  = s3jni_utf8_to_jstring(jc, zDb, -1);
+  jTable = jDbName ? s3jni_utf8_to_jstring(jc, zTable, -1) : 0;
+  IFTHREW {
+    EXCEPTION_CLEAR;
+    s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
+  }else{
+    assert( pHook->jObj );
+    assert( pHook->midCallback );
+    assert( ps->jDb );
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+    if( isPre ) (*env)->CallVoidMethod(env, pHook->jObj, pHook->midCallback,
+                                       ps->jDb, (jint)opId, jDbName, jTable,
+                                       (jlong)iKey1, (jlong)iKey2);
+    else
+#endif
+    (*env)->CallVoidMethod(env, pHook->jObj, pHook->midCallback,
+                           (jint)opId, jDbName, jTable, (jlong)iKey1);
+    IFTHREW{
+      EXCEPTION_WARN_CALLBACK_THREW("sqlite3_(pre)update_hook() callback");
+      s3jni_db_exception(env, ps, 0,
+                         "sqlite3_(pre)update_hook() callback threw");
+    }
+  }
+  UNREF_L(jDbName);
+  UNREF_L(jTable);
+}
+
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+static void s3jni_preupdate_hook_impl(void * pState, sqlite3 *pDb, int opId,
+                                      const char *zDb, const char *zTable,
+                                      sqlite3_int64 iKey1, sqlite3_int64 iKey2){
+  return s3jni_updatepre_hook_impl(pState, pDb, opId, zDb, zTable,
+                                   iKey1, iKey2);
+}
+#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
+
+static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb,
+                                   const char *zTable, sqlite3_int64 nRowid){
+  return s3jni_updatepre_hook_impl(pState, NULL, opId, zDb, zTable, nRowid, 0);
+}
+
+#ifndef SQLITE_ENABLE_PREUPDATE_HOOK
+/* We need no-op impls for preupdate_{count,depth,blobwrite}() */
+JDECL(int,1preupdate_1blobwrite)(JENV_CSELF, jobject jDb){ return SQLITE_MISUSE; }
+JDECL(int,1preupdate_1count)(JENV_CSELF, jobject jDb){ return SQLITE_MISUSE; }
+JDECL(int,1preupdate_1depth)(JENV_CSELF, jobject jDb){ return SQLITE_MISUSE; }
+#endif /* !SQLITE_ENABLE_PREUPDATE_HOOK */
+
+/*
+** JNI wrapper for both sqlite3_update_hook() and
+** sqlite3_preupdate_hook() (if isPre is true).
+*/
+static jobject s3jni_updatepre_hook(JNIEnv * env, int isPre, jobject jDb, jobject jHook){
+  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
+  jclass klazz;
+  jobject pOld = 0;
+  jmethodID xCallback;
+  S3JniHook * pHook = ps ? (
+    isPre ?
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+    &ps->hooks.preUpdate
+#else
+    0
+#endif
+    : &ps->hooks.update) : 0;
+
+  if(!pHook){
+    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(S3JniHook));
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+    if( isPre ) sqlite3_preupdate_hook(ps->pDb, 0, 0);
+    else
+#endif
+    sqlite3_update_hook(ps->pDb, 0, 0);
+    return pOld;
+  }
+  klazz = (*env)->GetObjectClass(env, jHook);
+  xCallback = isPre
+    ? (*env)->GetMethodID(env, klazz, "xPreUpdate",
+                          "(Lorg/sqlite/jni/sqlite3;"
+                          "I"
+                          "Ljava/lang/String;"
+                          "Ljava/lang/String;"
+                          "JJ)V")
+    : (*env)->GetMethodID(env, klazz, "xUpdateHook",
+                          "(ILjava/lang/String;Ljava/lang/String;J)V");
+  IFTHREW {
+    EXCEPTION_CLEAR;
+    s3jni_db_error(ps->pDb, SQLITE_ERROR,
+                   "Cannot not find matching callback on "
+                   "(pre)update hook object.");
+  }else{
+    pHook->midCallback = xCallback;
+    pHook->jObj = REF_G(jHook);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+    if( isPre ) sqlite3_preupdate_hook(ps->pDb, s3jni_preupdate_hook_impl, ps);
+    else
+#endif
+    sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps);
+    if(pOld){
+      jobject tmp = REF_L(pOld);
+      UNREF_G(pOld);
+      pOld = tmp;
+    }
+  }
+  return pOld;
+}
+
+
+JDECL(jobject,1preupdate_1hook)(JENV_CSELF, jobject jDb, jobject jHook){
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+  return s3jni_updatepre_hook(env, 1, jDb, jHook);
+#else
+  return NULL;
+#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
+}
+
+/* Impl for sqlite3_preupdate_{new,old}(). */
+static int s3jni_preupdate_newold(JNIEnv * const env, int isNew, jobject jDb,
+                                  jint iCol, jobject jOut){
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+  sqlite3_value * pOut = 0;
+  sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+  int rc;
+  int (*fOrig)(sqlite3*,int,sqlite3_value**) =
+    isNew ? sqlite3_preupdate_new : sqlite3_preupdate_old;
+  rc = fOrig(pDb, (int)iCol, &pOut);
+  if( 0==rc ){
+    jobject pWrap = new_sqlite3_value_wrapper(env, pOut);
+    if( pWrap ){
+      OutputPointer_set_sqlite3_value(env, jOut, pWrap);
+      UNREF_L(pWrap);
+    }else{
+      rc = SQLITE_NOMEM;
+    }
+  }
+  return rc;
+#else
+  return SQLITE_MISUSE;
+#endif
+}
+JDECL(jint,1preupdate_1new)(JENV_CSELF, jobject jDb, jint iCol, jobject jOut){
+  return s3jni_preupdate_newold(env, 1, jDb, iCol, jOut);
+}
+JDECL(jint,1preupdate_1old)(JENV_CSELF, jobject jDb, jint iCol, jobject jOut){
+  return s3jni_preupdate_newold(env, 0, jDb, iCol, jOut);
+}
+
+
 /* Central C-to-Java sqlite3_progress_handler() proxy. */
 static int s3jni_progress_handler_impl(void *pP){
   S3JniDb * const ps = (S3JniDb *)pP;
   LocalJniGetEnv;
-  int rc = (int)(*env)->CallIntMethod(env, ps->progress.jObj,
-                                      ps->progress.midCallback);
+  int rc = (int)(*env)->CallIntMethod(env, ps->hooks.progress.jObj,
+                                      ps->hooks.progress.midCallback);
   IFTHREW{
     rc = s3jni_db_exception(env, ps, rc,
                             "sqlite3_progress_handler() callback threw");
@@ -2860,8 +3082,8 @@ JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress
   jmethodID xCallback;
   if( n<1 || !jProgress ){
     if(ps){
-      UNREF_G(ps->progress.jObj);
-      memset(&ps->progress, 0, sizeof(ps->progress));
+      UNREF_G(ps->hooks.progress.jObj);
+      memset(&ps->hooks.progress, 0, sizeof(ps->hooks.progress));
     }
     sqlite3_progress_handler(ps->pDb, 0, 0, 0);
     return;
@@ -2878,9 +3100,9 @@ JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress
                    "Cannot not find matching xCallback() on "
                    "ProgressHandler object.");
   }else{
-    UNREF_G(ps->progress.jObj);
-    ps->progress.midCallback = xCallback;
-    ps->progress.jObj = REF_G(jProgress);
+    UNREF_G(ps->hooks.progress.jObj);
+    ps->hooks.progress.midCallback = xCallback;
+    ps->hooks.progress.jObj = REF_G(jProgress);
     sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps);
   }
 }
@@ -3099,7 +3321,7 @@ static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1,
   S3JniDb * const ps = pState;
   LocalJniGetEnv;
   S3JniEnv * const jc = S3JniGlobal_env_cache(env);
-  S3JniHook const * const pHook = &ps->authHook;
+  S3JniHook const * const pHook = &ps->hooks.auth;
   jstring const s0 = z0 ? s3jni_utf8_to_jstring(jc, z0, -1) : 0;
   jstring const s1 = z1 ? s3jni_utf8_to_jstring(jc, z1, -1) : 0;
   jstring const s2 = z2 ? s3jni_utf8_to_jstring(jc, z2, -1) : 0;
@@ -3121,7 +3343,7 @@ static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1,
 
 JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){
   S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
-  S3JniHook * const pHook = ps ? &ps->authHook : 0;
+  S3JniHook * const pHook = ps ? &ps->hooks.auth : 0;
 
   if( !ps ) return SQLITE_MISUSE;
   else if( !jHook ){
@@ -3287,8 +3509,8 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){
     }
   }
   assert(jP);
-  rc = (int)(*env)->CallIntMethod(env, ps->trace.jObj,
-                                  ps->trace.midCallback,
+  rc = (int)(*env)->CallIntMethod(env, ps->hooks.trace.jObj,
+                                  ps->hooks.trace.midCallback,
                                   (jint)traceflag, jP, jX);
   IFTHREW{
     EXCEPTION_WARN_CALLBACK_THREW("sqlite3_trace_v2() callback");
@@ -3303,96 +3525,28 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){
 JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){
   S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
   jclass klazz;
+
   if( !traceMask || !jTracer ){
     if(ps){
-      UNREF_G(ps->trace.jObj);
-      memset(&ps->trace, 0, sizeof(ps->trace));
+      S3JniHook_unref(env, &ps->hooks.trace, 0);
     }
     return (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0);
   }
   if(!ps) return SQLITE_NOMEM;
   klazz = (*env)->GetObjectClass(env, jTracer);
-  ps->trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback",
+  ps->hooks.trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback",
                                               "(ILjava/lang/Object;Ljava/lang/Object;)I");
   IFTHREW {
     EXCEPTION_CLEAR;
     return s3jni_db_error(ps->pDb, SQLITE_ERROR,
                           "Cannot not find matching xCallback() on Tracer object.");
   }
-  ps->trace.jObj = REF_G(jTracer);
+  ps->hooks.trace.jObj = REF_G(jTracer);
   return sqlite3_trace_v2(ps->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){
-  S3JniDb * const ps = pState;
-  LocalJniGetEnv;
-  S3JniEnv * const jc = S3JniGlobal_env_cache(env);
-  jstring jDbName;
-  jstring jTable;
-  jDbName  = s3jni_utf8_to_jstring(jc, zDb, -1);
-  jTable = jDbName ? s3jni_utf8_to_jstring(jc, zTable, -1) : 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{
-      EXCEPTION_WARN_CALLBACK_THREW("sqlite3_update_hook() callback");
-      s3jni_db_exception(env, ps, 0,
-                         "sqlite3_update_hook() callback threw");
-    }
-  }
-  UNREF_L(jDbName);
-  UNREF_L(jTable);
-}
-
-
 JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){
-  S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
-  jclass klazz;
-  jobject pOld = 0;
-  jmethodID xCallback;
-  S3JniHook * const pHook = &ps->updateHook;
-  if(!ps){
-    s3jni_db_error(ps->pDb, SQLITE_MISUSE, 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(S3JniHook));
-    sqlite3_update_hook(ps->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(ps->pDb, SQLITE_ERROR,
-                   "Cannot not find matching callback on "
-                   "update hook object.");
-  }else{
-    pHook->midCallback = xCallback;
-    pHook->jObj = REF_G(jHook);
-    sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps);
-    if(pOld){
-      jobject tmp = REF_L(pOld);
-      UNREF_G(pOld);
-      pOld = tmp;
-    }
-  }
-  return pOld;
+  return s3jni_updatepre_hook(env, 0, jDb, jHook);
 }
 
 
index 0a8736f2a7ba275bb02c8093efc5908dbafdefb7..86fada95749276ad71f364afa8b060380f64cfe5 100644 (file)
@@ -1307,6 +1307,54 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v2
 JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v3
   (JNIEnv *, jclass, jobject, jbyteArray, jint, jint, jobject, jobject);
 
+/*
+ * Class:     org_sqlite_jni_SQLite3Jni
+ * Method:    sqlite3_preupdate_blobwrite
+ * Signature: (Lorg/sqlite/jni/sqlite3;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1blobwrite
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_SQLite3Jni
+ * Method:    sqlite3_preupdate_count
+ * Signature: (Lorg/sqlite/jni/sqlite3;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1count
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_SQLite3Jni
+ * Method:    sqlite3_preupdate_depth
+ * Signature: (Lorg/sqlite/jni/sqlite3;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1depth
+  (JNIEnv *, jclass, jobject);
+
+/*
+ * Class:     org_sqlite_jni_SQLite3Jni
+ * Method:    sqlite3_preupdate_hook
+ * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/PreUpdateHook;)Lorg/sqlite/jni/PreUpdateHook;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1hook
+  (JNIEnv *, jclass, jobject, jobject);
+
+/*
+ * Class:     org_sqlite_jni_SQLite3Jni
+ * Method:    sqlite3_preupdate_new
+ * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/sqlite3_value;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1new
+  (JNIEnv *, jclass, jobject, jint, jobject);
+
+/*
+ * Class:     org_sqlite_jni_SQLite3Jni
+ * Method:    sqlite3_preupdate_old
+ * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/sqlite3_value;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1old
+  (JNIEnv *, jclass, jobject, jint, jobject);
+
 /*
  * Class:     org_sqlite_jni_SQLite3Jni
  * Method:    sqlite3_progress_handler
index 416ad48e603d9bb42de686ff949300c2370965de..bf61656dd50c16aa921806a8fe536bf00fb262e3 100644 (file)
@@ -86,6 +86,28 @@ public final class OutputPointer {
     }
   }
 
+  /**
+     Output pointer for use with routines, such as sqlite3_prepupdate_new(),
+     which return a sqlite3_value handle via an output pointer. These
+     pointers can only be set by the JNI layer, not by client-level
+     code.
+  */
+  public static final class sqlite3_value {
+    private org.sqlite.jni.sqlite3_value value;
+    //! Initializes with a null value.
+    public sqlite3_value(){value = null;}
+    //! Sets the current value to null.
+    public void clear(){value = null;}
+    //! Returns the current value.
+    public final org.sqlite.jni.sqlite3_value get(){return value;}
+    //! Equivalent to calling get() then clear().
+    public final org.sqlite.jni.sqlite3_value take(){
+      final org.sqlite.jni.sqlite3_value v = value;
+      value = null;
+      return v;
+    }
+  }
+
   /**
      Output pointer for use with native routines which return integers via
      output pointers.
diff --git a/ext/jni/src/org/sqlite/jni/PreUpdateHook.java b/ext/jni/src/org/sqlite/jni/PreUpdateHook.java
new file mode 100644 (file)
index 0000000..d5d82c7
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+** 2023-08-23
+**
+** 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;
+
+/**
+   A callback for use with sqlite3_preupdate_hook().
+*/
+public interface PreUpdateHook {
+  /**
+     Must function as described for the sqlite3_preupdate_hook().
+     callback, with the slight signature change.
+
+     Must not throw. Any exceptions may emit debugging messages and
+     will be suppressed.
+  */
+  void xPreUpdate(sqlite3 db, int op, String dbName, String dbTable,
+                  long iKey1, long iKey2 );
+}
index e64ecf491483abb097df5e16283d5897aa84f608..c22d2fa60eb2877adcf050cbe200e28dfe97b8a0 100644 (file)
@@ -475,7 +475,7 @@ public final class SQLite3Jni {
   ** retained.
   **
   ** If not built with SQLITE_ENABLE_SQLLOG defined, this returns
-  ** SQLITE_RANGE.
+  ** SQLITE_MISUSE.
   */
   public static native int sqlite3_config( @Nullable SQLLog logger );
 
@@ -706,6 +706,69 @@ public final class SQLite3Jni {
     return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null);
   }
 
+  /**
+     If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+     acts as a proxy for C's sqlite3_preupdate_blobwrite(), else it returns
+     SQLITE_MISUSE with no side effects.
+  */
+  public static native int sqlite3_preupdate_blobwrite(@NotNull sqlite3 db);
+  /**
+     If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+     acts as a proxy for C's sqlite3_preupdate_count(), else it returns
+     SQLITE_MISUSE with no side effects.
+  */
+  public static native int sqlite3_preupdate_count(@NotNull sqlite3 db);
+  /**
+     If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+     acts as a proxy for C's sqlite3_preupdate_depth(), else it returns
+     SQLITE_MISUSE with no side effects.
+  */
+  public static native int sqlite3_preupdate_depth(@NotNull sqlite3 db);
+
+  /**
+     If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+     acts as a proxy for C's sqlite3_preupdate_hook(), else it returns null
+     with no side effects.
+  */
+  public static native PreUpdateHook sqlite3_preupdate_hook(@NotNull sqlite3 db,
+                                                            @Nullable PreUpdateHook hook);
+
+  /**
+     If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined,
+     this acts as a proxy for C's sqlite3_preupdate_new(), else it
+     returns SQLITE_MISUSE with no side effects.
+  */
+  public static native int sqlite3_preupdate_new(@NotNull sqlite3 db, int col,
+                                                 @NotNull OutputPointer.sqlite3_value out);
+
+  /**
+     Convenience wrapper for the 3-arg sqlite3_preupdate_new() which returns
+     null on error.
+  */
+  public static sqlite3_value sqlite3_preupdate_new(@NotNull sqlite3 db, int col){
+    final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value();
+    sqlite3_preupdate_new(db, col, out);
+    return out.take();
+  }
+
+  /**
+     If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined,
+     this acts as a proxy for C's sqlite3_preupdate_old(), else it
+     returns SQLITE_MISUSE with no side effects.
+  */
+  public static native int sqlite3_preupdate_old(@NotNull sqlite3 db, int col,
+                                                 @NotNull OutputPointer.sqlite3_value out);
+
+  /**
+     Convenience wrapper for the 3-arg sqlite3_preupdate_old() which returns
+     null on error.
+  */
+  public static sqlite3_value sqlite3_preupdate_old(@NotNull sqlite3 db, int col){
+    final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value();
+    sqlite3_preupdate_old(db, col, out);
+    return out.take();
+  }
+
   public static native void sqlite3_progress_handler(
     @NotNull sqlite3 db, int n, @Nullable ProgressHandler h
   );
index fc350814b4a2ee1da486b16eac40b824329a914f..616e9fed229bb4a3022ab6d70f213538a2480adf 100644 (file)
@@ -997,6 +997,7 @@ public class Tester1 implements Runnable {
     final ValueHolder<Integer> expectedOp = new ValueHolder<>(0);
     final UpdateHook theHook = new UpdateHook(){
         @SuppressWarnings("unchecked")
+        @Override
         public void xUpdateHook(int opId, String dbName, String tableName, long rowId){
           ++counter.value;
           if( 0!=expectedOp.value ){
@@ -1040,6 +1041,79 @@ public class Tester1 implements Runnable {
     sqlite3_close_v2(db);
   }
 
+  /**
+     This test is functionally identical to testUpdateHook(), only with a
+     different callback type.
+  */
+  private synchronized void testPreUpdateHook(){
+    final sqlite3 db = createNewDb();
+    final ValueHolder<Integer> counter = new ValueHolder<>(0);
+    final ValueHolder<Integer> expectedOp = new ValueHolder<>(0);
+    final PreUpdateHook theHook = new PreUpdateHook(){
+        @SuppressWarnings("unchecked")
+        @Override
+        public void xPreUpdate(sqlite3 db, int opId, String dbName, String dbTable,
+                               long iKey1, long iKey2 ){
+          ++counter.value;
+          switch( opId ){
+            case SQLITE_UPDATE:
+              affirm( 0 < sqlite3_preupdate_count(db) );
+              affirm( null != sqlite3_preupdate_new(db, 0) );
+              affirm( null != sqlite3_preupdate_old(db, 0) );
+              break;
+            case SQLITE_INSERT:
+              affirm( null != sqlite3_preupdate_new(db, 0) );
+              break;
+            case SQLITE_DELETE:
+              affirm( null != sqlite3_preupdate_old(db, 0) );
+              break;
+            default:
+              break;
+          }
+          if( 0!=expectedOp.value ){
+            affirm( expectedOp.value == opId );
+          }
+        }
+      };
+    PreUpdateHook oldHook = sqlite3_preupdate_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_preupdate_hook(db, theHook);
+    affirm( theHook == oldHook );
+    expectedOp.value = SQLITE_DELETE;
+    execSql(db, "DELETE FROM t where a='d'");
+    affirm( 5 == counter.value );
+    oldHook = sqlite3_preupdate_hook(db, null);
+    affirm( theHook == oldHook );
+    execSql(db, "update t set a='e' where a='b';");
+    affirm( 5 == counter.value );
+    oldHook = sqlite3_preupdate_hook(db, null);
+    affirm( null == oldHook );
+
+    final PreUpdateHook newHook = new PreUpdateHook(){
+        @Override
+        public void xPreUpdate(sqlite3 db, int opId, String dbName,
+                               String tableName, long iKey1, long iKey2){
+        }
+      };
+    oldHook = sqlite3_preupdate_hook(db, newHook);
+    affirm( null == oldHook );
+    execSql(db, "update t set a='h' where a='a'");
+    affirm( 5 == counter.value );
+    oldHook = sqlite3_preupdate_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 void testRollbackHook(){
     final sqlite3 db = createNewDb();
     final ValueHolder<Integer> counter = new ValueHolder<>(0);
index ff7bb357eb9620d044ba68356b418dad7665dd0f..ca1266d086822f36dafbed6ea68e2f0586189a8f 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Bind\sa\ssubset\sof\ssqlite3_config()\sto\sJNI:\sthreading\smodes\sand\ssqllog.
-D 2023-08-23T10:36:12.341
+C Bind\ssqlite3_preupdate_hook()\sand\sfriends\sto\sJNI.
+D 2023-08-23T13:17:37.782
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -232,11 +232,11 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c
 F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9
 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282
 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8
-F ext/jni/GNUmakefile 33abc2f4f4bbd5451d6be5e6f2e109c045cc326cd942d602a3908a0c7b3c6f49
+F ext/jni/GNUmakefile 14b7c3abd1ae8693203b08b0e06bb359f8924ad2243f15953e9c6e456ae317b5
 F ext/jni/README.md ddcc6be0c0d65f1e2fd687de9f40d38c82630fd61f83cc9550773caa19dd8be1
 F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3
-F ext/jni/src/c/sqlite3-jni.c 01c6cf041d1b9937a97c7700006a532d3b11fd4991931e31ffa7a777b97fba11
-F ext/jni/src/c/sqlite3-jni.h 44bcb4eb3517c089f8f24f1546ea66b350d0661a4b0fa148425c9a41cabf487d
+F ext/jni/src/c/sqlite3-jni.c 852c4812c9a3663d871cb334eaa60eb6fc22d67da47d4ff3868fdbfd6ebedb3a
+F ext/jni/src/c/sqlite3-jni.h c5cb0348efe4e5f3d125a240e2437e8475de14a586c2f859e2acdcde4116244d
 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892
 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd
 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c
@@ -250,14 +250,15 @@ F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7
 F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9
 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060
 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 8110d4cfb20884e8ed241de7420c615b040a9f9c441d9cff06f34833399244a8
-F ext/jni/src/org/sqlite/jni/OutputPointer.java 464ea85c3eba673a7b575545f69fcd8aeb398477a26d155d88cee3e2459e7802
+F ext/jni/src/org/sqlite/jni/OutputPointer.java bb09fee5ad51d10e58075de000f8c1a3622a6c4b6a390ef134b6add1bfb32ca1
+F ext/jni/src/org/sqlite/jni/PreUpdateHook.java dec00a706b58c67989f0ff56c4f0a703821d25b45c62dd7fed1b462049f15c26
 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc
 F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7ce7797f2c6c7fca2004ff12ce20f86
 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564
 F ext/jni/src/org/sqlite/jni/SQLFunction.java f697cf2a81c4119f2baf0682af689686f0466f1dd83dba00885f5603e693fe16
 F ext/jni/src/org/sqlite/jni/SQLLog.java c60610b35208416940822e834d61f08fbbe5d6e06b374b541b49e41fd56c9798
-F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 2de5729a33cf2636160eb6893a4234c99669521a352bfffbf60410bd493ebece
-F ext/jni/src/org/sqlite/jni/Tester1.java 4e17a30e9da15954ba71ef52beb5b347f312594a0facbaf86e1f29481c4dc65c
+F ext/jni/src/org/sqlite/jni/SQLite3Jni.java e99e073e3779d00e23842858276efac93c8b523193b77ff12469d12a0b6182ca
+F ext/jni/src/org/sqlite/jni/Tester1.java 05ae085ed040bcc10b51cd12076a4151eda478f9773dc00a85d0cddd3dcc01f7
 F ext/jni/src/org/sqlite/jni/TesterFts5.java de095e3b701fba0c56d7b8b2993dc22bcbaa9de8f992904a93729ad729a91576
 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d
 F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d
@@ -2093,8 +2094,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 6c92d884920e4ace54913fc60ceef6e43a4351f45a4cb3c4a0ed3d29d544a31b
-R ebb24a95583279229c99fb88e45995e0
+P fce8ecaf7f2e69a168978e6993e58c452c45f76c39da33f2869c9d947c16cab1
+R 28457b0903a4397220c04d68facc73da
 U stephan
-Z 0a740a88323f212cff509af7c6f7ae11
+Z d81ff8935be35c831285c9d98a32b81f
 # Remove this line to create a well-formed Fossil manifest.
index 23b760f68b6c0e0865c4328c74eaeeb24b01254f..9a861544e36c68a18605551f81959d63bfb627ea 100644 (file)
@@ -1 +1 @@
-fce8ecaf7f2e69a168978e6993e58c452c45f76c39da33f2869c9d947c16cab1
\ No newline at end of file
+d0c425b5c1d3aac5ead18a501a3760b4506d68d373cb3be484247042cf2fa8d4
\ No newline at end of file