diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListTables.kt b/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListTables.kt index 2379a49869..fe234680f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListTables.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListTables.kt @@ -88,9 +88,9 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign val CREATE_TABLE = """ CREATE TABLE $TABLE_NAME ( $ID INTEGER PRIMARY KEY AUTOINCREMENT, - $NAME TEXT UNIQUE NOT NULL, + $NAME TEXT NOT NULL, $DISTRIBUTION_ID TEXT UNIQUE NOT NULL, - $RECIPIENT_ID INTEGER UNIQUE REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}), + $RECIPIENT_ID INTEGER UNIQUE REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE, $ALLOWS_REPLIES INTEGER DEFAULT 1, $DELETION_TIMESTAMP INTEGER DEFAULT 0, $IS_UNKNOWN INTEGER DEFAULT 0, @@ -118,7 +118,7 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign CREATE TABLE $TABLE_NAME ( $ID INTEGER PRIMARY KEY AUTOINCREMENT, $LIST_ID INTEGER NOT NULL REFERENCES ${ListTable.TABLE_NAME} (${ListTable.ID}) ON DELETE CASCADE, - $RECIPIENT_ID INTEGER NOT NULL REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}), + $RECIPIENT_ID INTEGER NOT NULL REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE, $PRIVACY_MODE INTEGER DEFAULT 0 ) """ diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt index fbbaef7134..8d103140cb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt @@ -68,6 +68,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V207_AddChunkSizeCo import org.thoughtcrime.securesms.database.helpers.migration.V209_ClearRecipientPniFromAciColumn import org.thoughtcrime.securesms.database.helpers.migration.V210_FixPniPossibleColumns import org.thoughtcrime.securesms.database.helpers.migration.V211_ReceiptColumnRenames +import org.thoughtcrime.securesms.database.helpers.migration.V212_RemoveDistributionListUniqueConstraint /** * Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness. @@ -76,7 +77,7 @@ object SignalDatabaseMigrations { val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass) - const val DATABASE_VERSION = 211 + const val DATABASE_VERSION = 212 private val migrations: List> = listOf( 149 to V149_LegacyMigrations, @@ -141,7 +142,8 @@ object SignalDatabaseMigrations { // 208 was a bad migration that only manipulated data and did not change schema, replaced by 209 209 to V209_ClearRecipientPniFromAciColumn, 210 to V210_FixPniPossibleColumns, - 211 to V211_ReceiptColumnRenames + 211 to V211_ReceiptColumnRenames, + 212 to V212_RemoveDistributionListUniqueConstraint ) @JvmStatic diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V212_RemoveDistributionListUniqueConstraint.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V212_RemoveDistributionListUniqueConstraint.kt new file mode 100644 index 0000000000..ccff4d1af2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V212_RemoveDistributionListUniqueConstraint.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.database.helpers.migration + +import android.app.Application +import net.zetetic.database.sqlcipher.SQLiteDatabase +import org.signal.core.util.SqlUtil +import org.signal.core.util.Stopwatch +import org.signal.core.util.logging.Log + +/** + * The android app was the only one enforcing unique story names. + * It's been decided dupes are ok, so we get to do the table recreation dance. + */ +object V212_RemoveDistributionListUniqueConstraint : SignalDatabaseMigration { + + private val TAG = Log.tag(V212_RemoveDistributionListUniqueConstraint::class.java) + + override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + val stopwatch = Stopwatch("migration") + + rebuildMainTable(db) + stopwatch.split("main-table") + + rebuildMembershipTable(db) + stopwatch.split("members-table") + + val foreignKeyViolations: List = SqlUtil.getForeignKeyViolations(db, "distribution_list") + SqlUtil.getForeignKeyViolations(db, "distribution_list_member") + 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 rebuildMainTable(db: SQLiteDatabase) { + db.execSQL( + """ + CREATE TABLE distribution_list_tmp ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + distribution_id TEXT UNIQUE NOT NULL, + recipient_id INTEGER UNIQUE REFERENCES recipient (_id) ON DELETE CASCADE, + allows_replies INTEGER DEFAULT 1, + deletion_timestamp INTEGER DEFAULT 0, + is_unknown INTEGER DEFAULT 0, + privacy_mode INTEGER DEFAULT 0 + ) + """ + ) + + db.execSQL( + """ + INSERT INTO distribution_list_tmp + SELECT + _id, + name, + distribution_id, + recipient_id, + allows_replies, + deletion_timestamp, + is_unknown, + privacy_mode + FROM distribution_list + """ + ) + + db.execSQL("DROP TABLE distribution_list") + db.execSQL("ALTER TABLE distribution_list_tmp RENAME TO distribution_list") + } + + private fun rebuildMembershipTable(db: SQLiteDatabase) { + db.execSQL("DROP INDEX distribution_list_member_list_id_recipient_id_privacy_mode_index") + db.execSQL("DROP INDEX distribution_list_member_recipient_id") + + db.execSQL( + """ + CREATE TABLE distribution_list_member_tmp ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + list_id INTEGER NOT NULL REFERENCES distribution_list (_id) ON DELETE CASCADE, + recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE, + privacy_mode INTEGER DEFAULT 0 + ) + """ + ) + + db.execSQL( + """ + INSERT INTO distribution_list_member_tmp + SELECT + _id, + list_id, + recipient_id, + privacy_mode + FROM distribution_list_member + """ + ) + + db.execSQL("DROP TABLE distribution_list_member") + db.execSQL("ALTER TABLE distribution_list_member_tmp RENAME TO distribution_list_member") + + db.execSQL("CREATE UNIQUE INDEX distribution_list_member_list_id_recipient_id_privacy_mode_index ON distribution_list_member (list_id, recipient_id, privacy_mode)") + db.execSQL("CREATE INDEX distribution_list_member_recipient_id ON distribution_list_member (recipient_id)") + } +}