mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-08-04 12:24:00 +01:00
Clean up orphaned latest_revision_id references during backup restore.
This commit is contained in:
@@ -6490,6 +6490,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
.forEach { entry -> fixDuplicate(writableDatabase, entry.value) }
|
||||
}
|
||||
|
||||
// This section deletes things that would otherwise violate foreign key constraints
|
||||
writableDatabase.execSQL("DELETE FROM $TABLE_NAME WHERE $LATEST_REVISION_ID IS NOT NULL AND $LATEST_REVISION_ID NOT IN (SELECT $ID FROM $TABLE_NAME)")
|
||||
writableDatabase.execSQL("DELETE FROM $TABLE_NAME WHERE $ORIGINAL_MESSAGE_ID IS NOT NULL AND $ORIGINAL_MESSAGE_ID NOT IN (SELECT $ID FROM $TABLE_NAME)")
|
||||
writableDatabase.execSQL("DELETE FROM ${ReactionTable.TABLE_NAME} WHERE ${ReactionTable.MESSAGE_ID} NOT IN (SELECT $ID FROM $TABLE_NAME)")
|
||||
writableDatabase.execSQL("DELETE FROM ${StorySendTable.TABLE_NAME} WHERE ${StorySendTable.MESSAGE_ID} NOT IN (SELECT $ID FROM $TABLE_NAME)")
|
||||
|
||||
@@ -165,6 +165,36 @@ class EditMessageRevisionTest {
|
||||
assertThat(getLatestRevisionId(edit2Id)).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeDuplicatesRepairsOrphanedLatestRevisionIdInsteadOfLeavingForeignKeyViolation() {
|
||||
val originalId = insertOriginalMessage(sentTimeMillis = 1000)
|
||||
val edit1Id = insertEdit(originalSentTimestamp = 1000, editSentTimeMillis = 1001)
|
||||
val edit2Id = insertEdit(originalSentTimestamp = 1000, editSentTimeMillis = 1002)
|
||||
|
||||
assertThat(getLatestRevisionId(originalId)).isNotNull().isEqualTo(edit2Id)
|
||||
|
||||
// Simulate what a backup import can produce: the latest revision is gone (deleted as a duplicate, or never inserted), but the
|
||||
// earlier revisions still point at it via latest_revision_id. Foreign keys are disabled during import, so this is not caught until
|
||||
// the final integrity check.
|
||||
SignalDatabase.writableDatabase.execSQL("PRAGMA foreign_keys=OFF")
|
||||
SignalDatabase.writableDatabase.execSQL("DELETE FROM ${MessageTable.TABLE_NAME} WHERE ${MessageTable.ID} = ?", arrayOf(edit2Id))
|
||||
assertThat(getLatestRevisionId(originalId)).isNotNull().isEqualTo(edit2Id)
|
||||
|
||||
SignalDatabase.messages.removeDuplicatesPostBackupRestore()
|
||||
|
||||
assertEquals("Orphaned latest_revision_id references must be cleaned up", 0, countDanglingLatestRevisionIds())
|
||||
SignalDatabase.writableDatabase.execSQL("PRAGMA foreign_keys=ON")
|
||||
}
|
||||
|
||||
private fun countDanglingLatestRevisionIds(): Int {
|
||||
return SignalDatabase.writableDatabase
|
||||
.query("SELECT COUNT(*) FROM ${MessageTable.TABLE_NAME} WHERE ${MessageTable.LATEST_REVISION_ID} IS NOT NULL AND ${MessageTable.LATEST_REVISION_ID} NOT IN (SELECT ${MessageTable.ID} FROM ${MessageTable.TABLE_NAME})")
|
||||
.use { cursor ->
|
||||
cursor.moveToFirst()
|
||||
cursor.getInt(0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertOriginalMessage(sentTimeMillis: Long): Long {
|
||||
val message = IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
|
||||
Reference in New Issue
Block a user