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})"
)
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(" ")

View file

@ -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,