From 85265412daa1bd32b49de68d71436f61043ce872 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 19 Feb 2026 16:01:08 +0000 Subject: [PATCH] Skip trigger drop/recreate in deleteMessagesInThread when there are no messages to delete. The deleteMessagesInThread method unconditionally drops and recreates FTS and MSL triggers in every call, even when there are no messages matching the delete criteria. Each trigger drop/create cycle changes the database schema cookie, causing SQLITE_SCHEMA errors for concurrent reader connections. --- .../securesms/database/MessageTable.kt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) 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 1ccc1b731b..9f2544a46f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt @@ -3952,11 +3952,25 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat fun deleteMessagesInThread(threadIds: Collection, extraWhere: String = ""): Int { var totalDeletedCount = 0 + val threadsWithPossibleDeletes = threadIds.filter { threadId -> + readableDatabase + .select(ID) + .from(TABLE_NAME) + .where("$THREAD_ID = $threadId $extraWhere") + .limit(1) + .run() + .use { it.moveToFirst() } + } + + if (threadsWithPossibleDeletes.isEmpty()) { + return 0 + } + writableDatabase.withinTransaction { db -> SignalDatabase.messageSearch.dropAfterMessageDeleteTrigger() SignalDatabase.messageLog.dropAfterMessageDeleteTrigger() - for (threadId in threadIds) { + for (threadId in threadsWithPossibleDeletes) { val subSelect = "SELECT ${TABLE_NAME}.$ID FROM $TABLE_NAME WHERE ${TABLE_NAME}.$THREAD_ID = $threadId $extraWhere LIMIT 1000" do { // Bulk deleting FK tables for large message delete efficiency