From 431e366e76b3c04d2b4a920262e18f3e3c76154c Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Tue, 14 Mar 2023 11:51:21 -0400 Subject: [PATCH] Add in possible recovery for DB error handler. A bad FTS index can result in the corruption handler being triggered. We can attempt to rebuild it to see if that helps. --- .../securesms/database/SearchTable.kt | 36 +++++++++++++++++-- .../database/SqlCipherErrorHandler.kt | 9 +++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SearchTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SearchTable.kt index 1a31005a1d..fdc61e66a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SearchTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SearchTable.kt @@ -34,20 +34,24 @@ class SearchTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa "CREATE VIRTUAL TABLE $FTS_TABLE_NAME USING fts5($BODY, $THREAD_ID UNINDEXED, content=${MessageTable.TABLE_NAME}, content_rowid=${MessageTable.ID})" ) + private const val TRIGGER_AFTER_INSERT = "message_ai" + private const val TRIGGER_AFTER_DELETE = "message_ad" + private const val TRIGGER_AFTER_UPDATE = "message_au" + @Language("sql") val CREATE_TRIGGERS = arrayOf( """ - CREATE TRIGGER message_ai AFTER INSERT ON ${MessageTable.TABLE_NAME} BEGIN + CREATE TRIGGER $TRIGGER_AFTER_INSERT AFTER INSERT ON ${MessageTable.TABLE_NAME} BEGIN INSERT INTO $FTS_TABLE_NAME($ID, $BODY, $THREAD_ID) VALUES (new.${MessageTable.ID}, new.${MessageTable.BODY}, new.${MessageTable.THREAD_ID}); END; """, """ - CREATE TRIGGER message_ad AFTER DELETE ON ${MessageTable.TABLE_NAME} BEGIN + CREATE TRIGGER $TRIGGER_AFTER_DELETE AFTER DELETE ON ${MessageTable.TABLE_NAME} BEGIN INSERT INTO $FTS_TABLE_NAME($FTS_TABLE_NAME, $ID, $BODY, $THREAD_ID) VALUES('delete', old.${MessageTable.ID}, old.${MessageTable.BODY}, old.${MessageTable.THREAD_ID}); END; """, """ - CREATE TRIGGER message_au AFTER UPDATE ON ${MessageTable.TABLE_NAME} BEGIN + CREATE TRIGGER $TRIGGER_AFTER_UPDATE AFTER UPDATE ON ${MessageTable.TABLE_NAME} BEGIN INSERT INTO $FTS_TABLE_NAME($FTS_TABLE_NAME, $ID, $BODY, $THREAD_ID) VALUES('delete', old.${MessageTable.ID}, old.${MessageTable.BODY}, old.${MessageTable.THREAD_ID}); INSERT INTO $FTS_TABLE_NAME($ID, $BODY, $THREAD_ID) VALUES (new.${MessageTable.ID}, new.${MessageTable.BODY}, new.${MessageTable.THREAD_ID}); END; @@ -217,6 +221,32 @@ class SearchTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa return true } + /** + * Drops all tables and recreates them. Should only be done in extreme circumstances. + */ + fun fullyResetTables() { + Log.w(TAG, "[fullyResetTables] Dropping tables and triggers...") + writableDatabase.execSQL("DROP TABLE IF EXISTS $FTS_TABLE_NAME") + writableDatabase.execSQL("DROP TABLE IF EXISTS ${FTS_TABLE_NAME}_config") + writableDatabase.execSQL("DROP TABLE IF EXISTS ${FTS_TABLE_NAME}_content") + writableDatabase.execSQL("DROP TABLE IF EXISTS ${FTS_TABLE_NAME}_data") + writableDatabase.execSQL("DROP TABLE IF EXISTS ${FTS_TABLE_NAME}_idx") + writableDatabase.execSQL("DROP TRIGGER IF EXISTS $TRIGGER_AFTER_INSERT") + writableDatabase.execSQL("DROP TRIGGER IF EXISTS $TRIGGER_AFTER_DELETE") + writableDatabase.execSQL("DROP TRIGGER IF EXISTS $TRIGGER_AFTER_UPDATE") + + Log.w(TAG, "[fullyResetTables] Recreating table...") + CREATE_TABLE.forEach { writableDatabase.execSQL(it) } + + Log.w(TAG, "[fullyResetTables] Recreating triggers...") + CREATE_TRIGGERS.forEach { writableDatabase.execSQL(it) } + + Log.w(TAG, "[fullyResetTables] Rebuilding index...") + rebuildIndex() + + Log.w(TAG, "[fullyResetTables] Done") + } + private fun createFullTextSearchQuery(query: String): String { return query .split(" ") diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.kt index 10fbd62b79..246f3ba2ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.kt @@ -30,6 +30,7 @@ class SqlCipherErrorHandler(private val databaseName: String) : DatabaseErrorHan if (result is DiagnosticResults.Success) { if (result.pragma1Passes && result.pragma2Passes) { + attemptToClearFullTextSearchIndex() throw DatabaseCorruptedError_BothChecksPass(lines) } else if (!result.pragma1Passes && result.pragma2Passes) { throw DatabaseCorruptedError_NormalCheckFailsCipherCheckPasses(lines) @@ -138,6 +139,14 @@ class SqlCipherErrorHandler(private val databaseName: String) : DatabaseErrorHan } } + private fun attemptToClearFullTextSearchIndex() { + try { + SignalDatabase.messageSearch.fullyResetTables() + } catch (e: Throwable) { + Log.w(TAG, "Failed to clear full text search index.", e) + } + } + private sealed class DiagnosticResults(val logs: String) { class Success( val pragma1Passes: Boolean,