Add new active column to ThreadTable.

This commit is contained in:
Alex Hart
2023-07-13 10:16:01 -03:00
committed by Nicholas Tinsley
parent a65e9c76bc
commit b0ca66cc1a
9 changed files with 266 additions and 28 deletions

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.SignalDatabaseRule
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.UUID
@Suppress("ClassName")
class ThreadTableTest_active {
@Rule
@JvmField
val databaseRule = SignalDatabaseRule()
private lateinit var recipient: Recipient
@Before
fun setUp() {
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())))
}
@Test
fun givenActiveUnarchivedThread_whenIGetUnarchivedConversationList_thenIExpectThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.getUnarchivedConversationList(
ConversationFilter.OFF,
false,
0,
10
).use { threads ->
assertEquals(1, threads.count)
val record = ThreadTable.StaticReader(threads, InstrumentationRegistry.getInstrumentation().context).getNext()
assertNotNull(record)
assertEquals(record!!.recipient.id, recipient.id)
}
}
@Test
fun givenInactiveUnarchivedThread_whenIGetUnarchivedConversationList_thenIExpectNoThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.deleteConversation(threadId)
SignalDatabase.threads.getUnarchivedConversationList(
ConversationFilter.OFF,
false,
0,
10
).use { threads ->
assertEquals(0, threads.count)
}
val threadId2 = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
assertEquals(threadId2, threadId)
}
@Test
fun givenActiveArchivedThread_whenIGetUnarchivedConversationList_thenIExpectNoThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.setArchived(setOf(threadId), true)
SignalDatabase.threads.getUnarchivedConversationList(
ConversationFilter.OFF,
false,
0,
10
).use { threads ->
assertEquals(0, threads.count)
}
}
@Test
fun givenActiveArchivedThread_whenIGetArchivedConversationList_thenIExpectThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.setArchived(setOf(threadId), true)
SignalDatabase.threads.getArchivedConversationList(
ConversationFilter.OFF,
0,
10
).use { threads ->
assertEquals(1, threads.count)
}
}
@Test
fun givenInactiveArchivedThread_whenIGetArchivedConversationList_thenIExpectNoThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.deleteConversation(threadId)
SignalDatabase.threads.setArchived(setOf(threadId), true)
SignalDatabase.threads.getArchivedConversationList(
ConversationFilter.OFF,
0,
10
).use { threads ->
assertEquals(0, threads.count)
}
val threadId2 = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
assertEquals(threadId2, threadId)
}
@Test
fun givenActiveArchivedThread_whenIDeactivateThread_thenIExpectNoMessages() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.messages.getConversation(threadId).use {
assertEquals(1, it.count)
}
SignalDatabase.threads.deleteConversation(threadId)
SignalDatabase.messages.getConversation(threadId).use {
assertEquals(0, it.count)
}
}
}

View File

@@ -34,7 +34,7 @@ class SignalDatabaseRule(
private fun deleteAllThreads() {
if (deleteAllThreadsOnEachRun) {
SignalDatabase.messages.deleteAllThreads()
SignalDatabase.threads.clearForTests()
}
}
}

View File

@@ -369,7 +369,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
FROM $TABLE_NAME
INNER JOIN ${MembershipTable.TABLE_NAME} ON ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID} = $TABLE_NAME.$GROUP_ID
INNER JOIN ${ThreadTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} = $TABLE_NAME.$RECIPIENT_ID
WHERE $ACTIVE = 1 AND ${MembershipTable.TABLE_NAME}.${MembershipTable.RECIPIENT_ID} IN (${subquery.where})
WHERE $TABLE_NAME.$ACTIVE = 1 AND ${MembershipTable.TABLE_NAME}.${MembershipTable.RECIPIENT_ID} IN (${subquery.where})
GROUP BY ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID}
ORDER BY $TITLE COLLATE NOCASE ASC
"""
@@ -407,10 +407,10 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
val queryArgs: Array<String>
if (includeInactive) {
query = "${membershipQuery.where} AND ($ACTIVE = ? OR $TABLE_NAME.$RECIPIENT_ID IN (SELECT ${ThreadTable.RECIPIENT_ID} FROM ${ThreadTable.TABLE_NAME}))"
query = "${membershipQuery.where} AND ($TABLE_NAME.$ACTIVE = ? OR $TABLE_NAME.$RECIPIENT_ID IN (SELECT ${ThreadTable.RECIPIENT_ID} FROM ${ThreadTable.TABLE_NAME} WHERE ${ThreadTable.TABLE_NAME}.${ThreadTable.ACTIVE} = 1))"
queryArgs = membershipQuery.whereArgs + buildArgs(1)
} else {
query = "${membershipQuery.where} AND $ACTIVE = ?"
query = "${membershipQuery.where} AND $TABLE_NAME.$ACTIVE = ?"
queryArgs = membershipQuery.whereArgs + buildArgs(1)
}
@@ -464,10 +464,10 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
val caseInsensitiveQuery = buildCaseInsensitiveGlobPattern(inputQuery)
if (includeInactive) {
query = "$TITLE GLOB ? AND ($ACTIVE = ? OR $TABLE_NAME.$RECIPIENT_ID IN (SELECT ${ThreadTable.RECIPIENT_ID} FROM ${ThreadTable.TABLE_NAME}))"
query = "$TITLE GLOB ? AND ($TABLE_NAME.$ACTIVE = ? OR $TABLE_NAME.$RECIPIENT_ID IN (SELECT ${ThreadTable.RECIPIENT_ID} FROM ${ThreadTable.TABLE_NAME} WHERE ${ThreadTable.TABLE_NAME}.${ThreadTable.ACTIVE} = 1))"
queryArgs = buildArgs(caseInsensitiveQuery, 1)
} else {
query = "$TITLE GLOB ? AND $ACTIVE = ?"
query = "$TITLE GLOB ? AND $TABLE_NAME.$ACTIVE = ?"
queryArgs = buildArgs(caseInsensitiveQuery, 1)
}
@@ -590,7 +590,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
}
if (!includeInactive) {
query += " AND $ACTIVE = ?"
query += " AND $TABLE_NAME.$ACTIVE = ?"
args = appendArg(args, "1")
}
@@ -1313,7 +1313,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
) AS active_timestamp
FROM $TABLE_NAME INNER JOIN ${ThreadTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} = $TABLE_NAME.$RECIPIENT_ID
WHERE
$ACTIVE = 1 AND
$TABLE_NAME.$ACTIVE = 1 AND
(
$SHOW_AS_STORY_STATE = ${ShowAsStoryState.ALWAYS.code} OR
(

View File

@@ -131,7 +131,7 @@ public class MentionTable extends DatabaseTable implements RecipientIdDatabaseRe
void deleteAbandonedMentions() {
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
String where = MESSAGE_ID + " NOT IN (SELECT " + MessageTable.ID + " FROM " + MessageTable.TABLE_NAME + ") OR " + THREAD_ID + " NOT IN (SELECT " + ThreadTable.ID + " FROM " + ThreadTable.TABLE_NAME + ")";
String where = MESSAGE_ID + " NOT IN (SELECT " + MessageTable.ID + " FROM " + MessageTable.TABLE_NAME + ") OR " + THREAD_ID + " NOT IN (SELECT " + ThreadTable.ID + " FROM " + ThreadTable.TABLE_NAME + " WHERE " + ThreadTable.ACTIVE + " = 1)";
db.delete(TABLE_NAME, where, null);
}

View File

@@ -3562,7 +3562,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
fun deleteAbandonedMessages(): Int {
val deletes = writableDatabase
.delete(TABLE_NAME)
.where("$THREAD_ID NOT IN (SELECT _id FROM ${ThreadTable.TABLE_NAME})")
.where("$THREAD_ID NOT IN (SELECT _id FROM ${ThreadTable.TABLE_NAME} WHERE ${ThreadTable.ACTIVE} = 1)")
.run()
if (deletes > 0) {

View File

@@ -406,6 +406,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
$TABLE_NAME.$SEEN_INVITE_REMINDER < ${InsightsBannerTier.TIER_TWO.id} AND
${ThreadTable.TABLE_NAME}.${ThreadTable.HAS_SENT} AND
${ThreadTable.TABLE_NAME}.${ThreadTable.DATE} > ? AND
${ThreadTable.TABLE_NAME}.${ThreadTable.ACTIVE} = 1 AND
$TABLE_NAME.$HIDDEN = 0
ORDER BY ${ThreadTable.TABLE_NAME}.${ThreadTable.DATE} DESC LIMIT 50
"""
@@ -3344,13 +3345,16 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
return SqlUtil.Query(subquery, SqlUtil.buildArgs(0, 0, query, query, query, query))
}
/**
* Queries all contacts without an active thread.
*/
fun getAllContactsWithoutThreads(inputQuery: String): Cursor {
val query = SqlUtil.buildCaseInsensitiveGlobPattern(inputQuery)
//language=sql
val subquery = """
SELECT ${SEARCH_PROJECTION.joinToString(", ")} FROM $TABLE_NAME
WHERE $BLOCKED = ? AND $HIDDEN = ? AND NOT EXISTS (SELECT 1 FROM ${ThreadTable.TABLE_NAME} WHERE ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} = $TABLE_NAME.$ID LIMIT 1)
WHERE $BLOCKED = ? AND $HIDDEN = ? AND NOT EXISTS (SELECT 1 FROM ${ThreadTable.TABLE_NAME} WHERE ${ThreadTable.TABLE_NAME}.${ThreadTable.ACTIVE} = 1 AND ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} = $TABLE_NAME.$ID LIMIT 1)
AND (
$SORT_NAME GLOB ? OR
$USERNAME GLOB ? OR

View File

@@ -6,6 +6,7 @@ import android.content.Context
import android.database.Cursor
import android.database.MergeCursor
import android.net.Uri
import androidx.annotation.VisibleForTesting
import androidx.core.content.contentValuesOf
import com.fasterxml.jackson.annotation.JsonProperty
import org.json.JSONObject
@@ -106,6 +107,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
const val LAST_SCROLLED = "last_scrolled"
const val PINNED = "pinned"
const val UNREAD_SELF_MENTION_COUNT = "unread_self_mention_count"
const val ACTIVE = "active"
const val MAX_CACHE_SIZE = 1000
@@ -134,16 +136,18 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
$HAS_SENT INTEGER DEFAULT 0,
$LAST_SCROLLED INTEGER DEFAULT 0,
$PINNED INTEGER DEFAULT 0,
$UNREAD_SELF_MENTION_COUNT INTEGER DEFAULT 0
$UNREAD_SELF_MENTION_COUNT INTEGER DEFAULT 0,
$ACTIVE INTEGER DEFAULT 0
)
"""
@JvmField
val CREATE_INDEXS = arrayOf(
"CREATE INDEX IF NOT EXISTS thread_recipient_id_index ON $TABLE_NAME ($RECIPIENT_ID);",
"CREATE INDEX IF NOT EXISTS archived_count_index ON $TABLE_NAME ($ARCHIVED, $MEANINGFUL_MESSAGES);",
"CREATE INDEX IF NOT EXISTS thread_recipient_id_index ON $TABLE_NAME ($RECIPIENT_ID, $ACTIVE);",
"CREATE INDEX IF NOT EXISTS archived_count_index ON $TABLE_NAME ($ACTIVE, $ARCHIVED, $MEANINGFUL_MESSAGES, $PINNED);",
"CREATE INDEX IF NOT EXISTS thread_pinned_index ON $TABLE_NAME ($PINNED);",
"CREATE INDEX IF NOT EXISTS thread_read ON $TABLE_NAME ($READ);"
"CREATE INDEX IF NOT EXISTS thread_read ON $TABLE_NAME ($READ);",
"CREATE INDEX IF NOT EXISTS thread_active ON $TABLE_NAME ($ACTIVE);"
)
private val THREAD_PROJECTION = arrayOf(
@@ -240,7 +244,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
STATUS to status,
DELIVERY_RECEIPT_COUNT to deliveryReceiptCount,
READ_RECEIPT_COUNT to readReceiptCount,
EXPIRES_IN to expiresIn
EXPIRES_IN to expiresIn,
ACTIVE to 1
)
writableDatabase
@@ -873,7 +878,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
return readableDatabase
.select("COUNT(*)")
.from(TABLE_NAME)
.where("$ARCHIVED = 1 AND $MEANINGFUL_MESSAGES != 0 $filterQuery")
.where("$ACTIVE = 1 AND $ARCHIVED = 1 AND $MEANINGFUL_MESSAGES != 0 $filterQuery")
.run()
.use { cursor ->
if (cursor.moveToFirst()) {
@@ -889,7 +894,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
return readableDatabase
.select("COUNT(*)")
.from(TABLE_NAME)
.where("$ARCHIVED = 0 AND $PINNED != 0 $filterQuery")
.where("$ACTIVE = 1 AND $ARCHIVED = 0 AND $PINNED != 0 $filterQuery")
.run()
.use { cursor ->
if (cursor.moveToFirst()) {
@@ -905,7 +910,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
return readableDatabase
.select("COUNT(*)")
.from(TABLE_NAME)
.where("$ARCHIVED = 0 AND ($MEANINGFUL_MESSAGES != 0 OR $PINNED != 0) $filterQuery")
.where("$ACTIVE = 1 AND $ARCHIVED = 0 AND ($MEANINGFUL_MESSAGES != 0 OR $PINNED != 0) $filterQuery")
.run()
.use { cursor ->
if (cursor.moveToFirst()) {
@@ -968,7 +973,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
for (threadId in threadIds) {
pinnedCount++
db.update(TABLE_NAME)
.values(PINNED to pinnedCount)
.values(PINNED to pinnedCount, ACTIVE to 1)
.where("$ID = ?", threadId)
.run()
}
@@ -1058,9 +1063,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
writableDatabase.withinTransaction { db ->
messages.deleteThread(threadId)
drafts.clearDrafts(threadId)
db.delete(TABLE_NAME)
.where("$ID = ?", threadId)
.run()
db.deactivateThread(threadId)
synchronized(threadIdCache) {
threadIdCache.remove(recipientIdForThreadId)
}
@@ -1078,7 +1081,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
val queries: List<SqlUtil.Query> = SqlUtil.buildCollectionQuery(ID, selectedConversations)
writableDatabase.withinTransaction { db ->
for (query in queries) {
db.delete(TABLE_NAME, query.where, query.whereArgs)
db.deactivateThread(query)
}
messages.deleteAbandonedMessages()
@@ -1100,13 +1103,21 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
ConversationUtil.clearShortcuts(context, recipientIds)
}
@VisibleForTesting
fun clearForTests() {
writableDatabase.withinTransaction {
deleteAllConversations()
it.delete(TABLE_NAME).run()
}
}
@SuppressLint("DiscouragedApi")
fun deleteAllConversations() {
writableDatabase.withinTransaction { db ->
messageLog.deleteAll()
messages.deleteAllThreads()
drafts.clearAllDrafts()
db.delete(TABLE_NAME, null, null)
db.deactivateThreads()
calls.deleteAllCalls()
synchronized(threadIdCache) {
threadIdCache.clear()
@@ -1316,7 +1327,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
if (pinnedRecipient != null) {
db.update(TABLE_NAME)
.values(PINNED to pinnedPosition)
.values(PINNED to pinnedPosition, ACTIVE to 1)
.where("$RECIPIENT_ID = ?", pinnedRecipient.id)
.run()
}
@@ -1598,6 +1609,48 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
}
}
private fun SQLiteDatabase.deactivateThreads() {
deactivateThread(query = null)
}
private fun SQLiteDatabase.deactivateThread(threadId: Long) {
deactivateThread(SqlUtil.Query("$ID = ?", SqlUtil.buildArgs(threadId)))
}
private fun SQLiteDatabase.deactivateThread(query: SqlUtil.Query?) {
val update = update(TABLE_NAME)
.values(
DATE to 0,
MEANINGFUL_MESSAGES to 0,
READ to ReadStatus.READ.serialize(),
TYPE to 0,
ERROR to 0,
SNIPPET to null,
SNIPPET_TYPE to 0,
SNIPPET_URI to null,
SNIPPET_CONTENT_TYPE to null,
SNIPPET_EXTRAS to null,
UNREAD_COUNT to 0,
ARCHIVED to 0,
STATUS to 0,
DELIVERY_RECEIPT_COUNT to 0,
READ_RECEIPT_COUNT to 0,
EXPIRES_IN to 0,
LAST_SEEN to 0,
HAS_SENT to 0,
LAST_SCROLLED to 0,
PINNED to 0,
UNREAD_SELF_MENTION_COUNT to 0,
ACTIVE to 0
)
if (query != null) {
update.where(query.where, query.whereArgs).run()
} else {
update.run()
}
}
private fun getAttachmentUriFor(record: MessageRecord): Uri? {
if (!record.isMms || record.isMmsNotification || record.isGroupAction) {
return null
@@ -1721,7 +1774,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
SELECT group_id, GROUP_CONCAT(${GroupTable.MembershipTable.TABLE_NAME}.${GroupTable.MembershipTable.RECIPIENT_ID}) as ${GroupTable.MEMBER_GROUP_CONCAT}
FROM ${GroupTable.MembershipTable.TABLE_NAME}
) as MembershipAlias ON MembershipAlias.${GroupTable.MembershipTable.GROUP_ID} = ${GroupTable.TABLE_NAME}.${GroupTable.GROUP_ID}
WHERE $where
WHERE $TABLE_NAME.$ACTIVE = 1 AND $where
ORDER BY $orderBy
"""

View File

@@ -54,6 +54,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V195_GroupMemberFor
import org.thoughtcrime.securesms.database.helpers.migration.V196_BackCallLinksWithRecipientV2
import org.thoughtcrime.securesms.database.helpers.migration.V197_DropAvatarColorFromCallLinks
import org.thoughtcrime.securesms.database.helpers.migration.V198_AddMacDigestColumn
import org.thoughtcrime.securesms.database.helpers.migration.V199_AddThreadActiveColumn
/**
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
@@ -62,7 +63,7 @@ object SignalDatabaseMigrations {
val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass)
const val DATABASE_VERSION = 198
const val DATABASE_VERSION = 199
@JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
@@ -265,6 +266,10 @@ object SignalDatabaseMigrations {
if (oldVersion < 198) {
V198_AddMacDigestColumn.migrate(context, db, oldVersion, newVersion)
}
if (oldVersion < 199) {
V199_AddThreadActiveColumn.migrate(context, db, oldVersion, newVersion)
}
}
@JvmStatic

View File

@@ -0,0 +1,32 @@
/*
* 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
/**
* Adds the ACTIVE column to the THREAD table, and mark all current threads active.
*
* For performance and maintainability reasons, instead of explicitly deleting thread rows from the
* database, we instead want to mark them as inactive and remove any backing data (messages, etc.),
* essentially turning them into tombstones, which can be 'resurrected' when a new attempt is made
* to chat with whomever the thread is tied to.
*/
@Suppress("ClassName")
object V199_AddThreadActiveColumn : SignalDatabaseMigration {
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("ALTER TABLE thread ADD COLUMN active INTEGER DEFAULT 0")
db.execSQL("UPDATE thread SET active = 1")
db.execSQL("DROP INDEX IF EXISTS thread_recipient_id_index")
db.execSQL("DROP INDEX IF EXISTS archived_count_index")
db.execSQL("CREATE INDEX IF NOT EXISTS thread_recipient_id_index ON thread (recipient_id, active)")
db.execSQL("CREATE INDEX IF NOT EXISTS archived_count_index ON thread (active, archived, meaningful_messages, pinned)")
db.execSQL("CREATE INDEX IF NOT EXISTS thread_active ON thread (active)")
}
}