mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 20:48:43 +00:00
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:
@@ -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(" ")
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user