Fix dangling quote authors.

This commit is contained in:
Greyson Parrelli 2024-12-09 23:23:25 -05:00
parent 3ea9dd5e1d
commit 01979b1c78
16 changed files with 165 additions and 49 deletions

View file

@ -526,7 +526,7 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
}
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
writableDatabase.update(TABLE_NAME)
val count = writableDatabase.update(TABLE_NAME)
.values(
contentValuesOf(
RECIPIENT_ID to toId.toLong()
@ -534,5 +534,7 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
)
.where("$RECIPIENT_ID = ?", fromId.toLong())
.run()
Log.d(TAG, "Remapped $fromId to $toId. count: $count")
}
}

View file

@ -1489,11 +1489,13 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
}
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
writableDatabase
val count = writableDatabase
.update(TABLE_NAME)
.values(PEER to toId.serialize())
.where("$PEER = ?", fromId)
.run()
Log.d(TAG, "Remapped $fromId to $toId. count: $count")
}
/**

View file

@ -525,12 +525,14 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign
.run()
}
override fun remapRecipient(oldId: RecipientId, newId: RecipientId) {
writableDatabase
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
val count = writableDatabase
.update(MembershipTable.TABLE_NAME)
.values(MembershipTable.RECIPIENT_ID to newId.serialize())
.where("${MembershipTable.RECIPIENT_ID} = ?", oldId)
.values(MembershipTable.RECIPIENT_ID to toId.serialize())
.where("${MembershipTable.RECIPIENT_ID} = ?", fromId)
.run(SQLiteDatabase.CONFLICT_REPLACE)
Log.d(TAG, "Remapped $fromId to $toId. count: $count")
}
fun deleteList(distributionListId: DistributionListId, deletionTimestamp: Long = System.currentTimeMillis()) {

View file

@ -8,6 +8,7 @@ import org.signal.core.util.SqlUtil
import org.signal.core.util.delete
import org.signal.core.util.deleteAll
import org.signal.core.util.forEach
import org.signal.core.util.logging.Log
import org.signal.core.util.readToList
import org.signal.core.util.requireBoolean
import org.signal.core.util.requireInt
@ -20,6 +21,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId
class GroupReceiptTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference {
companion object {
private val TAG = Log.tag(GroupReceiptTable::class)
const val TABLE_NAME = "group_receipts"
private const val ID = "_id"
const val MMS_ID = "mms_id"
@ -174,11 +177,13 @@ class GroupReceiptTable(context: Context?, databaseHelper: SignalDatabase?) : Da
}
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
writableDatabase
val count = writableDatabase
.update(TABLE_NAME)
.values(RECIPIENT_ID to toId.serialize())
.where("$RECIPIENT_ID = ?", fromId)
.run()
Log.d(TAG, "Remapped $fromId to $toId. count: $count")
}
private fun Cursor.toGroupReceiptInfo(): GroupReceiptInfo {

View file

@ -6,6 +6,7 @@ import org.signal.core.util.SqlUtil
import org.signal.core.util.delete
import org.signal.core.util.deleteAll
import org.signal.core.util.insertInto
import org.signal.core.util.logging.Log
import org.signal.core.util.readToList
import org.signal.core.util.requireInt
import org.signal.core.util.requireLong
@ -18,6 +19,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId
class MentionTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference, ThreadIdDatabaseReference {
companion object {
private val TAG = Log.tag(MentionTable::class)
const val TABLE_NAME = "mention"
const val ID = "_id"
const val THREAD_ID = "thread_id"
@ -165,11 +168,13 @@ class MentionTable(context: Context, databaseHelper: SignalDatabase) : DatabaseT
}
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
writableDatabase
val count = writableDatabase
.update("$TABLE_NAME INDEXED BY $RECIPIENT_ID_INDEX")
.values(RECIPIENT_ID to toId.serialize())
.where("$RECIPIENT_ID = ?", fromId)
.run()
Log.d(TAG, "Remapped $fromId to $toId. count: $count")
}
override fun remapThread(fromId: Long, toId: Long) {

View file

@ -11,6 +11,7 @@ import org.signal.core.util.readToList
import org.signal.core.util.requireBoolean
import org.signal.core.util.requireLong
import org.signal.core.util.toInt
import org.signal.core.util.update
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.MessageLogEntry
import org.thoughtcrime.securesms.recipients.Recipient
@ -419,16 +420,14 @@ class MessageSendLogTables constructor(context: Context?, databaseHelper: Signal
writableDatabase.execSQL(MslPayloadTable.AFTER_MESSAGE_DELETE_TRIGGER)
}
override fun remapRecipient(oldRecipientId: RecipientId, newRecipientId: RecipientId) {
val values = ContentValues().apply {
put(MslRecipientTable.RECIPIENT_ID, newRecipientId.serialize())
}
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
val count = writableDatabase
.update(MslRecipientTable.TABLE_NAME)
.values(MslRecipientTable.RECIPIENT_ID to toId.serialize())
.where("${MslRecipientTable.RECIPIENT_ID} = ?", fromId)
.run()
val db = databaseHelper.signalWritableDatabase
val query = "${MslRecipientTable.RECIPIENT_ID} = ?"
val args = SqlUtil.buildArgs(oldRecipientId.serialize())
db.update(MslRecipientTable.TABLE_NAME, values, query, args)
Log.d(TAG, "Remapped $fromId to $toId. count: $count")
}
private data class RecipientDevice(val recipientId: RecipientId, val devices: List<Int>)

View file

@ -4803,17 +4803,25 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
writableDatabase
val fromCount = writableDatabase
.update(TABLE_NAME)
.values(FROM_RECIPIENT_ID to toId.serialize())
.where("$FROM_RECIPIENT_ID = ?", fromId)
.run()
writableDatabase
val toCount = writableDatabase
.update(TABLE_NAME)
.values(TO_RECIPIENT_ID to toId.serialize())
.where("$TO_RECIPIENT_ID = ?", fromId)
.run()
val quoteAuthorCount = writableDatabase
.update(TABLE_NAME)
.values(QUOTE_AUTHOR to toId.serialize())
.where("$QUOTE_AUTHOR = ?", fromId)
.run()
Log.d(TAG, "Remapped $fromId to $toId. fromRecipient: $fromCount, toRecipient: $toCount, quoteAuthor: $quoteAuthorCount")
}
override fun remapThread(fromId: Long, toId: Long) {

View file

@ -7,11 +7,13 @@ import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteConstraintException
import org.signal.core.util.SqlUtil
import org.signal.core.util.logging.Log
import org.signal.core.util.requireBoolean
import org.signal.core.util.requireInt
import org.signal.core.util.requireLong
import org.signal.core.util.requireString
import org.signal.core.util.toInt
import org.signal.core.util.update
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
@ -25,6 +27,8 @@ import java.time.DayOfWeek
class NotificationProfileTables(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference {
companion object {
private val TAG = Log.tag(NotificationProfileTable::class)
@JvmField
val CREATE_TABLE: Array<String> = arrayOf(NotificationProfileTable.CREATE_TABLE, NotificationProfileScheduleTable.CREATE_TABLE, NotificationProfileAllowedMembersTable.CREATE_TABLE)
@ -296,16 +300,15 @@ class NotificationProfileTables(context: Context, databaseHelper: SignalDatabase
AppDependencies.databaseObserver.notifyNotificationProfileObservers()
}
override fun remapRecipient(oldId: RecipientId, newId: RecipientId) {
val query = "${NotificationProfileAllowedMembersTable.RECIPIENT_ID} = ?"
val args = SqlUtil.buildArgs(oldId)
val values = ContentValues().apply {
put(NotificationProfileAllowedMembersTable.RECIPIENT_ID, newId.serialize())
}
databaseHelper.signalWritableDatabase.update(NotificationProfileAllowedMembersTable.TABLE_NAME, values, query, args)
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
val count = writableDatabase
.update(NotificationProfileAllowedMembersTable.TABLE_NAME)
.values(NotificationProfileAllowedMembersTable.RECIPIENT_ID to toId.serialize())
.where("${NotificationProfileAllowedMembersTable.RECIPIENT_ID} = ?", fromId)
.run()
AppDependencies.databaseObserver.notifyNotificationProfileObservers()
Log.d(TAG, "Remapped $fromId to $toId. count: $count")
}
private fun getProfile(cursor: Cursor): NotificationProfile {

View file

@ -478,7 +478,9 @@ public final class PaymentTable extends DatabaseTable implements RecipientIdData
public void remapRecipient(@NonNull RecipientId fromId, @NonNull RecipientId toId) {
ContentValues values = new ContentValues();
values.put(RECIPIENT_ID, toId.serialize());
getWritableDatabase().update(TABLE_NAME, values, RECIPIENT_ID + " = ?", SqlUtil.buildArgs(fromId));
int count = getWritableDatabase().update(TABLE_NAME, values, RECIPIENT_ID + " = ?", SqlUtil.buildArgs(fromId));
Log.d(TAG, "Remapped " + fromId + " to " + toId + ". count: " + count);
}
public boolean markPaymentSubmitted(@NonNull UUID uuid,

View file

@ -121,11 +121,13 @@ class PendingPniSignatureMessageTable(context: Context, databaseHelper: SignalDa
writableDatabase.deleteAll(TABLE_NAME)
}
override fun remapRecipient(oldId: RecipientId, newId: RecipientId) {
writableDatabase
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
val count = writableDatabase
.update(TABLE_NAME)
.values(RECIPIENT_ID to newId.serialize())
.where("$RECIPIENT_ID = ?", oldId)
.values(RECIPIENT_ID to toId.serialize())
.where("$RECIPIENT_ID = ?", fromId)
.run()
Log.d(TAG, "Remapped $fromId to $toId. count: $count")
}
}

View file

@ -8,6 +8,7 @@ import androidx.annotation.NonNull;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.model.PendingRetryReceiptModel;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.recipients.RecipientId;
@ -24,6 +25,8 @@ import java.util.List;
*/
public final class PendingRetryReceiptTable extends DatabaseTable implements RecipientIdDatabaseReference, ThreadIdDatabaseReference {
private static final String TAG = Log.tag(PendingRetryReceiptTable.class);
public static final String TABLE_NAME = "pending_retry_receipts";
private static final String ID = "_id";
@ -87,9 +90,11 @@ public final class PendingRetryReceiptTable extends DatabaseTable implements Rec
public void remapRecipient(@NonNull RecipientId fromId, @NonNull RecipientId toId) {
ContentValues values = new ContentValues();
values.put(AUTHOR, toId.serialize());
getWritableDatabase().update(TABLE_NAME, values, AUTHOR + " = ?", SqlUtil.buildArgs(fromId));
int count = getWritableDatabase().update(TABLE_NAME, values, AUTHOR + " = ?", SqlUtil.buildArgs(fromId));
AppDependencies.getPendingRetryReceiptCache().clear();
Log.d(TAG, "Remapped " + fromId + " to " + toId + ". count: " + count);
}
@Override

View file

@ -7,6 +7,7 @@ import org.signal.core.util.CursorUtil
import org.signal.core.util.SqlUtil
import org.signal.core.util.delete
import org.signal.core.util.forEach
import org.signal.core.util.logging.Log
import org.signal.core.util.select
import org.signal.core.util.update
import org.thoughtcrime.securesms.database.model.MessageId
@ -20,6 +21,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId
class ReactionTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference {
companion object {
private val TAG = Log.tag(ReactionTable::class)
const val TABLE_NAME = "reaction"
private const val ID = "_id"
@ -167,14 +170,14 @@ class ReactionTable(context: Context, databaseHelper: SignalDatabase) : Database
}
}
override fun remapRecipient(oldAuthorId: RecipientId, newAuthorId: RecipientId) {
val query = "$AUTHOR_ID = ?"
val args = SqlUtil.buildArgs(oldAuthorId)
val values = ContentValues().apply {
put(AUTHOR_ID, newAuthorId.serialize())
}
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
val count = writableDatabase
.update(TABLE_NAME)
.values(AUTHOR_ID to toId.serialize())
.where("$AUTHOR_ID = ?", fromId)
.run()
readableDatabase.update(TABLE_NAME, values, query, args)
Log.d(TAG, "Remapped $fromId to $toId. count: $count")
}
fun deleteAbandonedReactions() {

View file

@ -5,6 +5,7 @@ import android.content.Context
import androidx.core.content.contentValuesOf
import org.signal.core.util.CursorUtil
import org.signal.core.util.SqlUtil
import org.signal.core.util.logging.Log
import org.signal.core.util.readToList
import org.signal.core.util.requireLong
import org.signal.core.util.select
@ -26,6 +27,8 @@ import org.whispersystems.signalservice.api.push.DistributionId
class StorySendTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference {
companion object {
private val TAG = Log.tag(StorySendTable::class)
const val TABLE_NAME = "story_sends"
const val ID = "_id"
const val MESSAGE_ID = "message_id"
@ -210,12 +213,14 @@ class StorySendTable(context: Context, databaseHelper: SignalDatabase) : Databas
return null
}
override fun remapRecipient(oldId: RecipientId, newId: RecipientId) {
val query = "$RECIPIENT_ID = ?"
val args = SqlUtil.buildArgs(oldId)
val values = contentValuesOf(RECIPIENT_ID to newId.serialize())
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
val count = writableDatabase
.update(TABLE_NAME)
.values(RECIPIENT_ID to toId.serialize())
.where("$RECIPIENT_ID = ?", fromId.serialize())
.run()
writableDatabase.update(TABLE_NAME, values, query, args)
Log.d(TAG, "Remapped $fromId to $toId. count: $count")
}
/**

View file

@ -116,6 +116,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V256_FixIncremental
import org.thoughtcrime.securesms.database.helpers.migration.V257_CreateBackupMediaSyncTable
import org.thoughtcrime.securesms.database.helpers.migration.V258_FixGroupRevokedInviteeUpdate
import org.thoughtcrime.securesms.database.helpers.migration.V259_AdjustNotificationProfileMidnightEndTimes
import org.thoughtcrime.securesms.database.helpers.migration.V260_RemapQuoteAuthors
/**
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
@ -234,10 +235,11 @@ object SignalDatabaseMigrations {
256 to V256_FixIncrementalDigestColumns,
257 to V257_CreateBackupMediaSyncTable,
258 to V258_FixGroupRevokedInviteeUpdate,
259 to V259_AdjustNotificationProfileMidnightEndTimes
259 to V259_AdjustNotificationProfileMidnightEndTimes,
260 to V260_RemapQuoteAuthors
)
const val DATABASE_VERSION = 259
const val DATABASE_VERSION = 260
@JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {

View file

@ -0,0 +1,66 @@
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import net.zetetic.database.sqlcipher.SQLiteDatabase
import org.signal.core.util.logging.Log
import org.signal.core.util.readToSingleLong
import org.signal.core.util.select
/**
* Previously, we weren't properly remapping quote authors when recipients were remapped. This repairs those scenarios the best we can.
*/
@Suppress("ClassName")
object V260_RemapQuoteAuthors : SignalDatabaseMigration {
private val TAG = Log.tag(V260_RemapQuoteAuthors::class)
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
// The following queries are really expensive without an index. So we create a temporary one.
db.execSQL("CREATE INDEX tmp_quote_author ON message (quote_author)")
// Even with an index, the updates can be a little expensive, so we try to figure out if we need them at all by using a quick check.
val invalidQuoteCount = db
.select("count(*)")
.from("message INDEXED BY tmp_quote_author")
.where("quote_author != 0 AND quote_author NOT IN (SELECT _id FROM recipient)")
.run()
.readToSingleLong()
if (invalidQuoteCount == 0L) {
Log.i(TAG, "No invalid quote authors, can skip migration.")
db.execSQL("DROP INDEX tmp_quote_author")
return
}
// Remap all quote_authors using a remapped recipient
db.execSQL(
"""
UPDATE
message INDEXED BY tmp_quote_author
SET
quote_author = (SELECT new_id FROM remapped_recipients WHERE old_id = message.quote_author)
WHERE
quote_author IN (SELECT old_id FROM remapped_recipients)
"""
)
// If there are any remaining quote_authors that don't reference a real recipient, we have no choice but to clear the quote
db.execSQL(
"""
UPDATE
message INDEXED BY tmp_quote_author
SET
quote_id = 0,
quote_author = 0,
quote_body = null,
quote_missing = 0,
quote_mentions = null,
quote_type = 0
WHERE
quote_author != 0 AND quote_author NOT IN (SELECT _id FROM recipient)
"""
)
db.execSQL("DROP INDEX tmp_quote_author")
}
}

View file

@ -163,9 +163,10 @@ public class ApplicationMigrations {
static final int AEP_INTRODUCTION = 119;
static final int GROUP_EXTRAS_DB_FIX = 120;
static final int EMOJI_SEARCH_INDEX_CHECK_2 = 121;
static final int QUOTE_AUTHOR_FIX = 122;
}
public static final int CURRENT_VERSION = 121;
public static final int CURRENT_VERSION = 122;
/**
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
@ -748,6 +749,10 @@ public class ApplicationMigrations {
jobs.put(Version.EMOJI_SEARCH_INDEX_CHECK_2, new EmojiSearchIndexCheckMigrationJob());
}
if (lastSeenVersion < Version.QUOTE_AUTHOR_FIX) {
jobs.put(Version.QUOTE_AUTHOR_FIX, new DatabaseMigrationJob());
}
return jobs;
}