Backfill missing errorType property for OWSRecoverableDecryptionPlaceholder(s)

This commit is contained in:
Sasha Weiss 2026-06-09 16:26:43 -07:00 committed by GitHub
parent d12641a3a2
commit cad4022e68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 105 additions and 10 deletions

View File

@ -333,6 +333,7 @@ public class GRDBSchemaMigrator {
case addMimeTypeToMessageAttachmentReference
case purgeMyStoryDeletedAtTimestamp
case addRecoverablePlaceholderExpirationIndex
case backfillRecoverablePlaceholderErrorType
// NOTE: Every time we add a migration id, consider
// incrementing grdbSchemaVersionLatest.
@ -5214,6 +5215,11 @@ public class GRDBSchemaMigrator {
return .success(())
}
migrator.registerMigration(.backfillRecoverablePlaceholderErrorType) { tx in
try Self.backfillRecoverablePlaceholderErrorType(tx: tx)
return .success(())
}
// MARK: - Schema Migration Insertion Point
}
@ -5573,6 +5579,15 @@ public class GRDBSchemaMigrator {
// MARK: - Migrations
static func backfillRecoverablePlaceholderErrorType(tx: DBWriteTransaction) throws {
try tx.database.execute(sql: """
UPDATE model_TSInteraction
INDEXED BY index_interactions_on_recoverable_placeholder_expiration
SET errorType = \(TSErrorMessageType.decryptionFailure.rawValue)
WHERE recordType = \(SDSRecordType.recoverableDecryptionPlaceholder.rawValue)
""")
}
static func addMimeTypeToMessageAttachmentReference(tx: DBWriteTransaction) throws {
try tx.database.alter(table: "MessageAttachmentReference") { table in
// In practice, this column will be NOT NULL. However, we can't

View File

@ -383,17 +383,27 @@ public class InteractionFinder: NSObject {
ORDER BY \(interactionColumn: .receivedAtTimestamp)
"""
let cursor = TSInteraction.grdbFetchCursor(
sql: sql,
transaction: tx,
)
var cursor = FailIfThrowsRecordCursor {
try InteractionRecord.fetchCursor(
tx.database,
sql: sql,
)
}
while let interactionRecord = cursor.next() {
let interaction: TSInteraction
do {
interaction = try TSInteraction.fromRecord(interactionRecord)
} catch {
owsFailDebug("Failed to deserialize TSInteraction! \(error)")
continue
}
guard let placeholder = interaction as? OWSRecoverableDecryptionPlaceholder else {
owsFailDebug("TSInteraction was not an OWSRecoverableDecryptionPlaceholder?")
continue
}
while
// Silently skip malformed TSInteraction rows, lest we become stuck
// on a malformed row forever.
let interaction = try? cursor.next(),
let placeholder = interaction as? OWSRecoverableDecryptionPlaceholder
{
return placeholder
}

View File

@ -1642,4 +1642,74 @@ struct GRDBSchemaMigratorTest {
false, // regular mp4
])
}
@Test
func testBackfillRecoverablePlaceholderErrorType() throws {
let placeholderRecordType = SDSRecordType.recoverableDecryptionPlaceholder.rawValue
let errorMessageRecordType = SDSRecordType.errorMessage.rawValue
let decryptionFailure = TSErrorMessageType.decryptionFailure.rawValue
let nonBlockingIdentityChange = TSErrorMessageType.nonBlockingIdentityChange.rawValue
let databaseQueue = DatabaseQueue()
try databaseQueue.write { db in
try db.execute(sql: """
CREATE TABLE "model_TSInteraction" (
id INTEGER PRIMARY KEY,
recordType INTEGER NOT NULL,
uniqueId TEXT NOT NULL UNIQUE,
receivedAtTimestamp INTEGER NOT NULL,
errorType INTEGER
);
""")
try db.execute(sql: """
CREATE INDEX "index_interactions_on_recoverable_placeholder_expiration"
ON "model_TSInteraction"(receivedAtTimestamp)
WHERE recordType = \(placeholderRecordType);
""")
try db.execute(
sql: """
INSERT INTO "model_TSInteraction" (id, recordType, uniqueId, receivedAtTimestamp, errorType)
VALUES (?, ?, ?, ?, ?), (?, ?, ?, ?, ?), (?, ?, ?, ?, ?), (?, ?, ?, ?, ?)
""",
arguments: [
1,
placeholderRecordType,
"A",
1000,
nil,
2,
placeholderRecordType,
"B",
2000,
nonBlockingIdentityChange,
3,
errorMessageRecordType,
"C",
3000,
nil,
4,
errorMessageRecordType,
"D",
4000,
nonBlockingIdentityChange,
],
)
let tx = DBWriteTransaction(database: db)
defer { tx.finalizeTransaction() }
try GRDBSchemaMigrator.backfillRecoverablePlaceholderErrorType(tx: tx)
}
let errorTypes = try databaseQueue.read { db in
try Int32?.fetchAll(db, sql: """
SELECT errorType FROM model_TSInteraction ORDER BY id
""")
}
#expect(errorTypes == [
decryptionFailure, // placeholder, was NULL -> backfilled
decryptionFailure, // placeholder, errorType corrected
nil, // non-placeholder, untouched
nonBlockingIdentityChange, // non-placeholder, untouched
])
}
}