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.
This commit is contained in:
Greyson Parrelli
2023-03-14 11:51:21 -04:00
parent 66cb2a04c3
commit 431e366e76
2 changed files with 42 additions and 3 deletions

View File

@@ -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})" "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") @Language("sql")
val CREATE_TRIGGERS = arrayOf( 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}); INSERT INTO $FTS_TABLE_NAME($ID, $BODY, $THREAD_ID) VALUES (new.${MessageTable.ID}, new.${MessageTable.BODY}, new.${MessageTable.THREAD_ID});
END; 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}); 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; 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($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}); INSERT INTO $FTS_TABLE_NAME($ID, $BODY, $THREAD_ID) VALUES (new.${MessageTable.ID}, new.${MessageTable.BODY}, new.${MessageTable.THREAD_ID});
END; END;
@@ -217,6 +221,32 @@ class SearchTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
return true 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 { private fun createFullTextSearchQuery(query: String): String {
return query return query
.split(" ") .split(" ")

View File

@@ -30,6 +30,7 @@ class SqlCipherErrorHandler(private val databaseName: String) : DatabaseErrorHan
if (result is DiagnosticResults.Success) { if (result is DiagnosticResults.Success) {
if (result.pragma1Passes && result.pragma2Passes) { if (result.pragma1Passes && result.pragma2Passes) {
attemptToClearFullTextSearchIndex()
throw DatabaseCorruptedError_BothChecksPass(lines) throw DatabaseCorruptedError_BothChecksPass(lines)
} else if (!result.pragma1Passes && result.pragma2Passes) { } else if (!result.pragma1Passes && result.pragma2Passes) {
throw DatabaseCorruptedError_NormalCheckFailsCipherCheckPasses(lines) 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) { private sealed class DiagnosticResults(val logs: String) {
class Success( class Success(
val pragma1Passes: Boolean, val pragma1Passes: Boolean,