Bind sqlite3_preupdate_hook() and friends to JNI.

FossilOrigin-Name: d0c425b5c1d3aac5ead18a501a3760b4506d68d373cb3be484247042cf2fa8d4
This commit is contained in:
stephan 2023-08-23 13:17:37 +00:00
parent 4e97ab4296
commit bea9ed0f1f
9 changed files with 545 additions and 153 deletions

View 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 \

View 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);
}

View 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

View 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.

View File

@ -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 );
}

View 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
);

View 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);

View File

@ -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.

View File

@ -1 +1 @@
fce8ecaf7f2e69a168978e6993e58c452c45f76c39da33f2869c9d947c16cab1
d0c425b5c1d3aac5ead18a501a3760b4506d68d373cb3be484247042cf2fa8d4