139 lines
5.3 KiB
Swift
139 lines
5.3 KiB
Swift
//
|
|
// Copyright 2025 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import SignalServiceKit
|
|
|
|
class LazyDatabaseMigratorRunner: BGProcessingTaskRunner {
|
|
private let databaseStorage: SDSDatabaseStorage
|
|
|
|
init(
|
|
databaseStorage: SDSDatabaseStorage,
|
|
) {
|
|
self.databaseStorage = databaseStorage
|
|
}
|
|
|
|
static var taskIdentifier: String = "LazyDatabaseMigratorTask"
|
|
|
|
static var requiresNetworkConnectivity: Bool = false
|
|
public static let requiresExternalPower = false
|
|
|
|
func startCondition() -> BGProcessingTaskStartCondition {
|
|
do {
|
|
let indexes = try databaseStorage.read { tx in
|
|
let db = tx.database
|
|
return Set(try String.fetchAll(db, sql: "SELECT name FROM sqlite_master WHERE type = 'index'"))
|
|
}
|
|
let lazilyRemovedIndexes = [
|
|
"index_interactions_on_uniqueId_and_threadUniqueId",
|
|
"index_interactions_on_expiresInSeconds_and_expiresAt",
|
|
"index_model_TSInteraction_on_uniqueThreadId_and_attachmentIds",
|
|
"index_interactions_on_timestamp_sourceDeviceId_and_authorUUID",
|
|
"index_interactions_on_timestamp_sourceDeviceId_and_authorPhoneNumber",
|
|
"index_model_TSInteraction_on_uniqueThreadId_and_hasEnded_and_recordType",
|
|
"index_model_TSInteraction_on_uniqueThreadId_and_eraId_and_recordType",
|
|
"index_model_TSInteraction_on_StoryContext",
|
|
"index_model_TSInteraction_ConversationLoadInteractionCount",
|
|
"index_model_TSInteraction_ConversationLoadInteractionDistance",
|
|
]
|
|
if !indexes.isDisjoint(with: lazilyRemovedIndexes) {
|
|
return .asSoonAsPossible
|
|
}
|
|
let lazilyInsertedIndexes = [
|
|
"Interaction_disappearingMessages_partial",
|
|
"Interaction_timestamp",
|
|
"Interaction_unendedGroupCall_partial",
|
|
"Interaction_groupCallEraId_partial",
|
|
"Interaction_storyReply_partial",
|
|
]
|
|
if !indexes.isSuperset(of: lazilyInsertedIndexes) {
|
|
return .asSoonAsPossible
|
|
}
|
|
return .never
|
|
} catch {
|
|
Logger.warn("Couldn't check if we need to execute.")
|
|
return .never
|
|
}
|
|
}
|
|
|
|
/// Run the migrations.
|
|
///
|
|
/// If you encounter an error in this method, you can update
|
|
/// `simulatePriorCancellation` to return true and run on a simulator.
|
|
func run() async throws {
|
|
// Must be idempotent.
|
|
|
|
try Task.checkCancellation()
|
|
await databaseStorage.awaitableWrite { tx in
|
|
Logger.info("Removing threadUniqueId/uniqueId index.")
|
|
try! GRDBSchemaMigrator.removeInteractionThreadUniqueIdUniqueIdIndex(tx: tx)
|
|
}
|
|
|
|
try Task.checkCancellation()
|
|
await databaseStorage.awaitableWrite { tx in
|
|
Logger.info("Rebuilding disappearing messages index.")
|
|
try! GRDBSchemaMigrator.rebuildDisappearingMessagesIndex(tx: tx)
|
|
}
|
|
|
|
try Task.checkCancellation()
|
|
await databaseStorage.awaitableWrite { tx in
|
|
Logger.info("Removing attachmentIds index.")
|
|
try! GRDBSchemaMigrator.removeInteractionAttachmentIdsIndex(tx: tx)
|
|
}
|
|
|
|
try Task.checkCancellation()
|
|
await databaseStorage.awaitableWrite { tx in
|
|
Logger.info("Rebuilding timestamp index.")
|
|
try! GRDBSchemaMigrator.rebuildInteractionTimestampIndex(tx: tx)
|
|
}
|
|
|
|
try Task.checkCancellation()
|
|
await databaseStorage.awaitableWrite { tx in
|
|
Logger.info("Rebuilding unended groupCall index.")
|
|
try! GRDBSchemaMigrator.rebuildInteractionUnendedGroupCallIndex(tx: tx)
|
|
}
|
|
|
|
try Task.checkCancellation()
|
|
await databaseStorage.awaitableWrite { tx in
|
|
Logger.info("Rebuilding groupCall/eraId index.")
|
|
try! GRDBSchemaMigrator.rebuildInteractionGroupCallEraIdIndex(tx: tx)
|
|
}
|
|
|
|
try Task.checkCancellation()
|
|
await databaseStorage.awaitableWrite { tx in
|
|
Logger.info("Rebuilding story message index.")
|
|
try! GRDBSchemaMigrator.rebuildInteractionStoryReplyIndex(tx: tx)
|
|
}
|
|
|
|
try Task.checkCancellation()
|
|
await databaseStorage.awaitableWrite { tx in
|
|
Logger.info("Removing conversation load count index.")
|
|
try! GRDBSchemaMigrator.removeInteractionConversationLoadCountIndex(tx: tx)
|
|
}
|
|
|
|
try Task.checkCancellation()
|
|
await databaseStorage.awaitableWrite { tx in
|
|
Logger.info("Removing conversation load distance index.")
|
|
try! GRDBSchemaMigrator.removeInteractionConversationLoadDistanceIndex(tx: tx)
|
|
}
|
|
|
|
#if DEBUG
|
|
// If we just ran the migration, we shouldn't need to run it again. If this
|
|
// fails, the list of indexes and migrations we perform don't match.
|
|
owsAssertDebug(startCondition() == .never)
|
|
#endif
|
|
|
|
Logger.info("Done!")
|
|
}
|
|
|
|
#if targetEnvironment(simulator)
|
|
func simulatePriorCancellation() -> Bool {
|
|
// Simulates a prior cancellation that may cause the task to run when it's
|
|
// already finished.
|
|
return Int.random(in: 0..<10) == 0
|
|
}
|
|
#endif
|
|
}
|