From 577eaa1eae23acf9a3ce28af3cdb5334a2dc6d97 Mon Sep 17 00:00:00 2001 From: Michelle Tang Date: Mon, 9 Mar 2026 10:45:27 -0400 Subject: [PATCH] Avoid dropping column in message table. --- .../securesms/database/MessageTable.kt | 2 + .../migration/V302_AddDeletedByColumn.kt | 198 +----------------- 2 files changed, 4 insertions(+), 196 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt index 823dcbf2bb..56bf1a5b64 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt @@ -190,6 +190,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat const val UNIDENTIFIED = "unidentified" const val REACTIONS_UNREAD = "reactions_unread" const val REACTIONS_LAST_SEEN = "reactions_last_seen" + const val REMOTE_DELETED = "remote_deleted" const val SERVER_GUID = "server_guid" const val RECEIPT_TIMESTAMP = "receipt_timestamp" const val EXPORT_STATE = "export_state" @@ -276,6 +277,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat $VIEW_ONCE INTEGER DEFAULT 0, $REACTIONS_UNREAD INTEGER DEFAULT 0, $REACTIONS_LAST_SEEN INTEGER DEFAULT -1, + $REMOTE_DELETED INTEGER DEFAULT 0, $MENTIONS_SELF INTEGER DEFAULT 0, $NOTIFIED_TIMESTAMP INTEGER DEFAULT 0, $SERVER_GUID TEXT DEFAULT NULL, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V302_AddDeletedByColumn.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V302_AddDeletedByColumn.kt index c4cc008328..e47b8435d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V302_AddDeletedByColumn.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V302_AddDeletedByColumn.kt @@ -4,13 +4,12 @@ import android.app.Application import org.signal.core.util.SqlUtil import org.signal.core.util.Stopwatch import org.signal.core.util.logging.Log -import org.signal.core.util.readToList -import org.signal.core.util.requireNonNullString import org.thoughtcrime.securesms.database.SQLiteDatabase /** * Adds column to messages to track who has deleted a given message. Because of an - * OOM crash, we rebuild the table manually in batches instead of using the drop column syntax. + * OOM crash, we do not drop the remote_deleted column. For users who already completed + * this migration, we add it back in the future. */ @Suppress("ClassName") object V302_AddDeletedByColumn : SignalDatabaseMigration { @@ -25,208 +24,15 @@ object V302_AddDeletedByColumn : SignalDatabaseMigration { val stopwatch = Stopwatch("migration", decimalPlaces = 2) - val dependentItems: List = getAllDependentItems(db, "message") - dependentItems.forEach { item -> - val sql = "DROP ${item.type} IF EXISTS ${item.name}" - Log.d(TAG, "Executing: $sql") - db.execSQL(sql) - } - stopwatch.split("drop-dependents") - db.execSQL("ALTER TABLE message ADD COLUMN deleted_by INTEGER DEFAULT NULL REFERENCES recipient (_id) ON DELETE CASCADE") stopwatch.split("add-column") db.execSQL("UPDATE message SET deleted_by = from_recipient_id WHERE remote_deleted > 0") stopwatch.split("update-data") - db.execSQL( - """ - CREATE TABLE message_tmp ( - _id INTEGER PRIMARY KEY AUTOINCREMENT, - date_sent INTEGER NOT NULL, - date_received INTEGER NOT NULL, - date_server INTEGER DEFAULT -1, - thread_id INTEGER NOT NULL REFERENCES thread (_id) ON DELETE CASCADE, - from_recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE, - from_device_id INTEGER, - to_recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE, - type INTEGER NOT NULL, - body TEXT, - read INTEGER DEFAULT 0, - ct_l TEXT, - exp INTEGER, - m_type INTEGER, - m_size INTEGER, - st INTEGER, - tr_id TEXT, - subscription_id INTEGER DEFAULT -1, - receipt_timestamp INTEGER DEFAULT -1, - has_delivery_receipt INTEGER DEFAULT 0, - has_read_receipt INTEGER DEFAULT 0, - viewed INTEGER DEFAULT 0, - mismatched_identities TEXT DEFAULT NULL, - network_failures TEXT DEFAULT NULL, - expires_in INTEGER DEFAULT 0, - expire_started INTEGER DEFAULT 0, - notified INTEGER DEFAULT 0, - quote_id INTEGER DEFAULT 0, - quote_author INTEGER DEFAULT 0, - quote_body TEXT DEFAULT NULL, - quote_missing INTEGER DEFAULT 0, - quote_mentions BLOB DEFAULT NULL, - quote_type INTEGER DEFAULT 0, - shared_contacts TEXT DEFAULT NULL, - unidentified INTEGER DEFAULT 0, - link_previews TEXT DEFAULT NULL, - view_once INTEGER DEFAULT 0, - reactions_unread INTEGER DEFAULT 0, - reactions_last_seen INTEGER DEFAULT -1, - mentions_self INTEGER DEFAULT 0, - notified_timestamp INTEGER DEFAULT 0, - server_guid TEXT DEFAULT NULL, - message_ranges BLOB DEFAULT NULL, - story_type INTEGER DEFAULT 0, - parent_story_id INTEGER DEFAULT 0, - export_state BLOB DEFAULT NULL, - exported INTEGER DEFAULT 0, - scheduled_date INTEGER DEFAULT -1, - latest_revision_id INTEGER DEFAULT NULL REFERENCES message (_id) ON DELETE CASCADE, - original_message_id INTEGER DEFAULT NULL REFERENCES message (_id) ON DELETE CASCADE, - revision_number INTEGER DEFAULT 0, - message_extras BLOB DEFAULT NULL, - expire_timer_version INTEGER DEFAULT 1 NOT NULL, - votes_unread INTEGER DEFAULT 0, - votes_last_seen INTEGER DEFAULT 0, - pinned_until INTEGER DEFAULT 0, - pinning_message_id INTEGER DEFAULT 0, - pinned_at INTEGER DEFAULT 0, - deleted_by INTEGER DEFAULT NULL REFERENCES recipient (_id) ON DELETE CASCADE - ) - """ - ) - stopwatch.split("create-table") - - copyMessages(db) - stopwatch.split("copy-data") - - db.execSQL("DROP TABLE message") - stopwatch.split("drop-old-table") - - db.execSQL("ALTER TABLE message_tmp RENAME TO message") - stopwatch.split("rename-table") - - dependentItems.forEach { item -> - val sql = item.createStatement - Log.d(TAG, "Executing: $sql") - db.execSQL(sql) - } - stopwatch.split("recreate-dependents") - db.execSQL("CREATE INDEX IF NOT EXISTS message_deleted_by_index ON message (deleted_by)") stopwatch.split("create-index") - val foreignKeyViolations: List = SqlUtil.getForeignKeyViolations(db, "message") - if (foreignKeyViolations.isNotEmpty()) { - Log.w(TAG, "Foreign key violations!\n${foreignKeyViolations.joinToString(separator = "\n")}") - throw IllegalStateException("Foreign key violations!") - } - stopwatch.split("fk-check") - stopwatch.stop(TAG) } - - private fun copyMessages(db: SQLiteDatabase) { - val batchSize = 50_000L - - val maxId = SqlUtil.getNextAutoIncrementId(db, "message") - - for (i in 1..maxId step batchSize) { - db.execSQL( - """ - INSERT INTO message_tmp - SELECT - _id, - date_sent, - date_received, - date_server, - thread_id, - from_recipient_id, - from_device_id, - to_recipient_id, - type, - body, - read, - ct_l, - exp, - m_type, - m_size, - st, - tr_id, - subscription_id, - receipt_timestamp, - has_delivery_receipt, - has_read_receipt, - viewed, - mismatched_identities, - network_failures, - expires_in, - expire_started, - notified, - quote_id, - quote_author, - quote_body, - quote_missing, - quote_mentions, - quote_type, - shared_contacts, - unidentified, - link_previews, - view_once, - reactions_unread, - reactions_last_seen, - mentions_self, - notified_timestamp, - server_guid, - message_ranges, - story_type, - parent_story_id, - export_state, - exported, - scheduled_date, - latest_revision_id, - original_message_id, - revision_number, - message_extras, - expire_timer_version, - votes_unread, - votes_last_seen, - pinned_until, - pinning_message_id, - pinned_at, - deleted_by - FROM - message - WHERE - _id >= $i AND - _id < ${i + batchSize} - """ - ) - } - } - - private fun getAllDependentItems(db: SQLiteDatabase, tableName: String): List { - return db.rawQuery("SELECT type, name, sql FROM sqlite_schema WHERE tbl_name='$tableName' AND type != 'table'").readToList { cursor -> - SqlItem( - type = cursor.requireNonNullString("type"), - name = cursor.requireNonNullString("name"), - createStatement = cursor.requireNonNullString("sql") - ) - } - } - - data class SqlItem( - val type: String, - val name: String, - val createStatement: String - ) }