Skip to content

Commit

Permalink
Add support for sqlite3_update_hook (#194)
Browse files Browse the repository at this point in the history
  • Loading branch information
jdicami authored Aug 6, 2024
1 parent 12e9161 commit 5b5c440
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

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

@MediumTest
@Test
public void testSetUpdateHook() {
// Initialize AtomicReferences with a default value
AtomicInteger calledOperation = new AtomicInteger();
AtomicReference<String> calledDatabaseName = new AtomicReference<>("");
AtomicReference<String> calledTableName = new AtomicReference<>("");
AtomicLong calledRowId = new AtomicLong();

// Set up the update hook
mDatabase.setUpdateHook((operationType, databaseName, tableName, rowId) -> {
calledOperation.set(operationType);
calledDatabaseName.set(databaseName);
calledTableName.set(tableName);
calledRowId.set(rowId);
});

// Execute SQL statements
mDatabase.execSQL("CREATE TABLE testUpdateHook (_id INTEGER PRIMARY KEY, data TEXT);");
mDatabase.execSQL("INSERT INTO testUpdateHook (data) VALUES ('newValue');");

// Verify that the update hook was called correctly
assertEquals(18, calledOperation.get());
assertEquals("main", calledDatabaseName.get());
assertEquals("testUpdateHook", calledTableName.get());
assertEquals(1, calledRowId.get());
}

@MediumTest
@Test
public void testVersion() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ private static native long nativeExecuteForCursorWindow(
private static native boolean nativeHasCodec();
private static native void nativeLoadExtension(long connectionPtr, String file, String proc);

private static native void nativeRegisterUpdateHook(long connectionPtr, SQLiteUpdateHook updateCallback);

public static boolean hasCodec(){ return nativeHasCodec(); }

private SQLiteConnection(SQLiteConnectionPool pool,
Expand Down Expand Up @@ -255,6 +257,12 @@ private void open() {
for (SQLiteCustomExtension extension : mConfiguration.customExtensions) {
nativeLoadExtension(mConnectionPtr, extension.path, extension.entryPoint);
}

final SQLiteUpdateHook sqliteUpdateHook = mConfiguration.sqliteUpdateHook;

if (sqliteUpdateHook != null) {
nativeRegisterUpdateHook(mConnectionPtr, sqliteUpdateHook);
}
}

private void dispose(boolean finalized) {
Expand Down Expand Up @@ -456,6 +464,11 @@ void reconfigure(SQLiteDatabaseConfiguration configuration) {
}
}

final SQLiteUpdateHook updateHook = configuration.sqliteUpdateHook;
if (updateHook != null) {
nativeRegisterUpdateHook(mConnectionPtr, updateHook);
}

// Remember what changed.
boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
!= mConfiguration.foreignKeyConstraintsEnabled;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,21 @@ public void addFunction(String name, int numArgs, Function function, int flags)
}
}

public void setUpdateHook(SQLiteUpdateHook updateHook) {
synchronized (mLock) {
throwIfNotOpenLocked();

mConfigurationLocked.sqliteUpdateHook = updateHook;

try {
mConnectionPoolLocked.reconfigure(mConfigurationLocked);
} catch (RuntimeException ex) {
mConfigurationLocked.sqliteUpdateHook = null;
throw ex;
}
}
}

/**
* Gets the database version.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public final class SQLiteDatabaseConfiguration {
*/
public @SQLiteDatabase.OpenFlags int openFlags;

public SQLiteUpdateHook sqliteUpdateHook;

/**
* The maximum size of the prepared statement cache for each database connection.
* Must be non-negative.
Expand Down Expand Up @@ -184,6 +186,7 @@ void updateParametersFrom(SQLiteDatabaseConfiguration other) {
customExtensions.addAll(other.customExtensions);
functions.clear();
functions.addAll(other.functions);
sqliteUpdateHook = other.sqliteUpdateHook;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,42 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
}
}

static void sqliteUpdateHookCallback(void *pArg, int operationType, const char *databaseName, const char *tableName, sqlite3_int64 rowId) {
JNIEnv* env = 0;
gpJavaVM->GetEnv((void**)&env, JNI_VERSION_1_4);

jobject updateCallbackObjGlobal = reinterpret_cast<jobject>(pArg);
jobject updateCallbackObj = env->NewLocalRef(updateCallbackObjGlobal);

// TODO: Do something magical
// Get the Java class and method ID
jclass updateHookManagerClass = env->GetObjectClass(updateCallbackObj);
jmethodID onUpdateMethod = env->GetMethodID(updateHookManagerClass, "onUpdateFromNative", "(ILjava/lang/String;Ljava/lang/String;J)V");

if (onUpdateMethod != NULL) {
// Create Java strings from C strings
jstring dbName = env->NewStringUTF(databaseName);
jstring tblName = env->NewStringUTF(tableName);

// Call the Java method
env->CallVoidMethod(updateCallbackObj, onUpdateMethod, operationType, dbName, tblName, rowId);

// Clean up local references
env->DeleteLocalRef(dbName);
env->DeleteLocalRef(tblName);
} else {
ALOGE("Failed to find onUpdateFromNative method");
}

env->DeleteLocalRef(updateCallbackObj);

if (env->ExceptionCheck()) {
ALOGE("An exception was thrown by custom update callback.");
/* LOGE_EX(env); */
env->ExceptionClear();
}
}

// Called each time a custom function is evaluated.
static void sqliteCustomFunctionCallback(sqlite3_context *context,
int argc, sqlite3_value **argv) {
Expand Down Expand Up @@ -373,6 +409,20 @@ static void nativeRegisterFunction(JNIEnv *env, jclass clazz, jlong connectionPt
}
}

static void nativeRegisterUpdateHook(JNIEnv* env, jclass clazz, jlong connectionPtr,
jobject updateCallbackObj) {
SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);

jobject updateCallbackObjGlobal = env->NewGlobalRef(updateCallbackObj);

if (updateCallbackObjGlobal == NULL) {
ALOGE("Failed to create global reference for callback object");
return;
}

sqlite3_update_hook(connection->db, sqliteUpdateHookCallback, reinterpret_cast<void*>(updateCallbackObjGlobal));
}

static void nativeRegisterLocalizedCollators(JNIEnv* env, jclass clazz, jlong connectionPtr,
jstring localeStr) {
SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
Expand Down Expand Up @@ -988,6 +1038,8 @@ static JNINativeMethod sMethods[] =
(void*)nativeHasCodec },
{ "nativeLoadExtension", "(JLjava/lang/String;Ljava/lang/String;)V",
(void*)nativeLoadExtension },
{ "nativeRegisterUpdateHook", "(JLio/requery/android/database/sqlite/SQLiteUpdateHook;)V",
(void*)nativeRegisterUpdateHook },
};

int register_android_database_SQLiteConnection(JNIEnv *env)
Expand Down

0 comments on commit 5b5c440

Please sign in to comment.