Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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