Skip to content

Commit 5b5c440

Browse files
authored
Add support for sqlite3_update_hook (#194)
1 parent 12e9161 commit 5b5c440

5 files changed

Lines changed: 115 additions & 0 deletions

File tree

sqlite-android/src/androidTest/java/io/requery/android/database/DatabaseGeneralTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
import java.util.Arrays;
4040
import java.util.List;
4141
import java.util.Locale;
42+
import java.util.concurrent.atomic.AtomicBoolean;
43+
import java.util.concurrent.atomic.AtomicInteger;
44+
import java.util.concurrent.atomic.AtomicLong;
45+
import java.util.concurrent.atomic.AtomicReference;
4246

4347
import androidx.test.core.app.ApplicationProvider;
4448
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -162,6 +166,34 @@ public void callback(Args args, Result result) {
162166
assertSame(null, cursor.getString(0));
163167
}
164168

169+
@MediumTest
170+
@Test
171+
public void testSetUpdateHook() {
172+
// Initialize AtomicReferences with a default value
173+
AtomicInteger calledOperation = new AtomicInteger();
174+
AtomicReference<String> calledDatabaseName = new AtomicReference<>("");
175+
AtomicReference<String> calledTableName = new AtomicReference<>("");
176+
AtomicLong calledRowId = new AtomicLong();
177+
178+
// Set up the update hook
179+
mDatabase.setUpdateHook((operationType, databaseName, tableName, rowId) -> {
180+
calledOperation.set(operationType);
181+
calledDatabaseName.set(databaseName);
182+
calledTableName.set(tableName);
183+
calledRowId.set(rowId);
184+
});
185+
186+
// Execute SQL statements
187+
mDatabase.execSQL("CREATE TABLE testUpdateHook (_id INTEGER PRIMARY KEY, data TEXT);");
188+
mDatabase.execSQL("INSERT INTO testUpdateHook (data) VALUES ('newValue');");
189+
190+
// Verify that the update hook was called correctly
191+
assertEquals(18, calledOperation.get());
192+
assertEquals("main", calledDatabaseName.get());
193+
assertEquals("testUpdateHook", calledTableName.get());
194+
assertEquals(1, calledRowId.get());
195+
}
196+
165197
@MediumTest
166198
@Test
167199
public void testVersion() {

sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteConnection.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ private static native long nativeExecuteForCursorWindow(
170170
private static native boolean nativeHasCodec();
171171
private static native void nativeLoadExtension(long connectionPtr, String file, String proc);
172172

173+
private static native void nativeRegisterUpdateHook(long connectionPtr, SQLiteUpdateHook updateCallback);
174+
173175
public static boolean hasCodec(){ return nativeHasCodec(); }
174176

175177
private SQLiteConnection(SQLiteConnectionPool pool,
@@ -255,6 +257,12 @@ private void open() {
255257
for (SQLiteCustomExtension extension : mConfiguration.customExtensions) {
256258
nativeLoadExtension(mConnectionPtr, extension.path, extension.entryPoint);
257259
}
260+
261+
final SQLiteUpdateHook sqliteUpdateHook = mConfiguration.sqliteUpdateHook;
262+
263+
if (sqliteUpdateHook != null) {
264+
nativeRegisterUpdateHook(mConnectionPtr, sqliteUpdateHook);
265+
}
258266
}
259267

260268
private void dispose(boolean finalized) {
@@ -456,6 +464,11 @@ void reconfigure(SQLiteDatabaseConfiguration configuration) {
456464
}
457465
}
458466

467+
final SQLiteUpdateHook updateHook = configuration.sqliteUpdateHook;
468+
if (updateHook != null) {
469+
nativeRegisterUpdateHook(mConnectionPtr, updateHook);
470+
}
471+
459472
// Remember what changed.
460473
boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
461474
!= mConfiguration.foreignKeyConstraintsEnabled;

sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDatabase.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,21 @@ public void addFunction(String name, int numArgs, Function function, int flags)
940940
}
941941
}
942942

943+
public void setUpdateHook(SQLiteUpdateHook updateHook) {
944+
synchronized (mLock) {
945+
throwIfNotOpenLocked();
946+
947+
mConfigurationLocked.sqliteUpdateHook = updateHook;
948+
949+
try {
950+
mConnectionPoolLocked.reconfigure(mConfigurationLocked);
951+
} catch (RuntimeException ex) {
952+
mConfigurationLocked.sqliteUpdateHook = null;
953+
throw ex;
954+
}
955+
}
956+
}
957+
943958
/**
944959
* Gets the database version.
945960
*

sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDatabaseConfiguration.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ public final class SQLiteDatabaseConfiguration {
6363
*/
6464
public @SQLiteDatabase.OpenFlags int openFlags;
6565

66+
public SQLiteUpdateHook sqliteUpdateHook;
67+
6668
/**
6769
* The maximum size of the prepared statement cache for each database connection.
6870
* Must be non-negative.
@@ -184,6 +186,7 @@ void updateParametersFrom(SQLiteDatabaseConfiguration other) {
184186
customExtensions.addAll(other.customExtensions);
185187
functions.clear();
186188
functions.addAll(other.functions);
189+
sqliteUpdateHook = other.sqliteUpdateHook;
187190
}
188191

189192
/**

sqlite-android/src/main/jni/sqlite/android_database_SQLiteConnection.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,42 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
221221
}
222222
}
223223

224+
static void sqliteUpdateHookCallback(void *pArg, int operationType, const char *databaseName, const char *tableName, sqlite3_int64 rowId) {
225+
JNIEnv* env = 0;
226+
gpJavaVM->GetEnv((void**)&env, JNI_VERSION_1_4);
227+
228+
jobject updateCallbackObjGlobal = reinterpret_cast<jobject>(pArg);
229+
jobject updateCallbackObj = env->NewLocalRef(updateCallbackObjGlobal);
230+
231+
// TODO: Do something magical
232+
// Get the Java class and method ID
233+
jclass updateHookManagerClass = env->GetObjectClass(updateCallbackObj);
234+
jmethodID onUpdateMethod = env->GetMethodID(updateHookManagerClass, "onUpdateFromNative", "(ILjava/lang/String;Ljava/lang/String;J)V");
235+
236+
if (onUpdateMethod != NULL) {
237+
// Create Java strings from C strings
238+
jstring dbName = env->NewStringUTF(databaseName);
239+
jstring tblName = env->NewStringUTF(tableName);
240+
241+
// Call the Java method
242+
env->CallVoidMethod(updateCallbackObj, onUpdateMethod, operationType, dbName, tblName, rowId);
243+
244+
// Clean up local references
245+
env->DeleteLocalRef(dbName);
246+
env->DeleteLocalRef(tblName);
247+
} else {
248+
ALOGE("Failed to find onUpdateFromNative method");
249+
}
250+
251+
env->DeleteLocalRef(updateCallbackObj);
252+
253+
if (env->ExceptionCheck()) {
254+
ALOGE("An exception was thrown by custom update callback.");
255+
/* LOGE_EX(env); */
256+
env->ExceptionClear();
257+
}
258+
}
259+
224260
// Called each time a custom function is evaluated.
225261
static void sqliteCustomFunctionCallback(sqlite3_context *context,
226262
int argc, sqlite3_value **argv) {
@@ -373,6 +409,20 @@ static void nativeRegisterFunction(JNIEnv *env, jclass clazz, jlong connectionPt
373409
}
374410
}
375411

412+
static void nativeRegisterUpdateHook(JNIEnv* env, jclass clazz, jlong connectionPtr,
413+
jobject updateCallbackObj) {
414+
SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
415+
416+
jobject updateCallbackObjGlobal = env->NewGlobalRef(updateCallbackObj);
417+
418+
if (updateCallbackObjGlobal == NULL) {
419+
ALOGE("Failed to create global reference for callback object");
420+
return;
421+
}
422+
423+
sqlite3_update_hook(connection->db, sqliteUpdateHookCallback, reinterpret_cast<void*>(updateCallbackObjGlobal));
424+
}
425+
376426
static void nativeRegisterLocalizedCollators(JNIEnv* env, jclass clazz, jlong connectionPtr,
377427
jstring localeStr) {
378428
SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
@@ -988,6 +1038,8 @@ static JNINativeMethod sMethods[] =
9881038
(void*)nativeHasCodec },
9891039
{ "nativeLoadExtension", "(JLjava/lang/String;Ljava/lang/String;)V",
9901040
(void*)nativeLoadExtension },
1041+
{ "nativeRegisterUpdateHook", "(JLio/requery/android/database/sqlite/SQLiteUpdateHook;)V",
1042+
(void*)nativeRegisterUpdateHook },
9911043
};
9921044

9931045
int register_android_database_SQLiteConnection(JNIEnv *env)

0 commit comments

Comments
 (0)