kopia lustrzana https://github.com/ryukoposting/Signal-Android
Re-run migration to add foreign key to thread table.
Unfortunately the table schema wasn't fixed for new installs, so we need to run it again.main
rodzic
74760a4a64
commit
df86d1b4ba
|
@ -105,7 +105,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||||
$ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
$ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
$DATE INTEGER DEFAULT 0,
|
$DATE INTEGER DEFAULT 0,
|
||||||
$MEANINGFUL_MESSAGES INTEGER DEFAULT 0,
|
$MEANINGFUL_MESSAGES INTEGER DEFAULT 0,
|
||||||
$RECIPIENT_ID INTEGER,
|
$RECIPIENT_ID INTEGER NOT NULL UNIQUE REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE,
|
||||||
$READ INTEGER DEFAULT ${ReadStatus.READ.serialize()},
|
$READ INTEGER DEFAULT ${ReadStatus.READ.serialize()},
|
||||||
$TYPE INTEGER DEFAULT 0,
|
$TYPE INTEGER DEFAULT 0,
|
||||||
$ERROR INTEGER DEFAULT 0,
|
$ERROR INTEGER DEFAULT 0,
|
||||||
|
@ -114,14 +114,14 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||||
$SNIPPET_URI TEXT DEFAULT NULL,
|
$SNIPPET_URI TEXT DEFAULT NULL,
|
||||||
$SNIPPET_CONTENT_TYPE TEXT DEFAULT NULL,
|
$SNIPPET_CONTENT_TYPE TEXT DEFAULT NULL,
|
||||||
$SNIPPET_EXTRAS TEXT DEFAULT NULL,
|
$SNIPPET_EXTRAS TEXT DEFAULT NULL,
|
||||||
|
$UNREAD_COUNT INTEGER DEFAULT 0,
|
||||||
$ARCHIVED INTEGER DEFAULT 0,
|
$ARCHIVED INTEGER DEFAULT 0,
|
||||||
$STATUS INTEGER DEFAULT 0,
|
$STATUS INTEGER DEFAULT 0,
|
||||||
$DELIVERY_RECEIPT_COUNT INTEGER DEFAULT 0,
|
$DELIVERY_RECEIPT_COUNT INTEGER DEFAULT 0,
|
||||||
|
$READ_RECEIPT_COUNT INTEGER DEFAULT 0,
|
||||||
$EXPIRES_IN INTEGER DEFAULT 0,
|
$EXPIRES_IN INTEGER DEFAULT 0,
|
||||||
$LAST_SEEN INTEGER DEFAULT 0,
|
$LAST_SEEN INTEGER DEFAULT 0,
|
||||||
$HAS_SENT INTEGER DEFAULT 0,
|
$HAS_SENT INTEGER DEFAULT 0,
|
||||||
$READ_RECEIPT_COUNT INTEGER DEFAULT 0,
|
|
||||||
$UNREAD_COUNT INTEGER DEFAULT 0,
|
|
||||||
$LAST_SCROLLED INTEGER DEFAULT 0,
|
$LAST_SCROLLED INTEGER DEFAULT 0,
|
||||||
$PINNED INTEGER DEFAULT 0,
|
$PINNED INTEGER DEFAULT 0,
|
||||||
$UNREAD_SELF_MENTION_COUNT INTEGER DEFAULT 0
|
$UNREAD_SELF_MENTION_COUNT INTEGER DEFAULT 0
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V167_RecreateReacti
|
||||||
import org.thoughtcrime.securesms.database.helpers.migration.V168_SingleMessageTableMigration
|
import org.thoughtcrime.securesms.database.helpers.migration.V168_SingleMessageTableMigration
|
||||||
import org.thoughtcrime.securesms.database.helpers.migration.V169_EmojiSearchIndexRank
|
import org.thoughtcrime.securesms.database.helpers.migration.V169_EmojiSearchIndexRank
|
||||||
import org.thoughtcrime.securesms.database.helpers.migration.V170_CallTableMigration
|
import org.thoughtcrime.securesms.database.helpers.migration.V170_CallTableMigration
|
||||||
|
import org.thoughtcrime.securesms.database.helpers.migration.V171_ThreadForeignKeyFix
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||||
|
@ -34,7 +35,7 @@ object SignalDatabaseMigrations {
|
||||||
|
|
||||||
val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass)
|
val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass)
|
||||||
|
|
||||||
const val DATABASE_VERSION = 170
|
const val DATABASE_VERSION = 171
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
|
@ -125,6 +126,10 @@ object SignalDatabaseMigrations {
|
||||||
if (oldVersion < 170) {
|
if (oldVersion < 170) {
|
||||||
V170_CallTableMigration.migrate(context, db, oldVersion, newVersion)
|
V170_CallTableMigration.migrate(context, db, oldVersion, newVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 171) {
|
||||||
|
V171_ThreadForeignKeyFix.migrate(context, db, oldVersion, newVersion)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
|
|
@ -0,0 +1,180 @@
|
||||||
|
package org.thoughtcrime.securesms.database.helpers.migration
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import net.zetetic.database.sqlcipher.SQLiteDatabase
|
||||||
|
import org.signal.core.util.Stopwatch
|
||||||
|
import org.signal.core.util.delete
|
||||||
|
import org.signal.core.util.logging.Log
|
||||||
|
import org.signal.core.util.readToList
|
||||||
|
import org.signal.core.util.requireLong
|
||||||
|
import org.signal.core.util.toSingleLine
|
||||||
|
import org.signal.core.util.update
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When we ran [V166_ThreadAndMessageForeignKeys], we forgot to update the actual table definition in [ThreadTable].
|
||||||
|
* We could make this conditional, but I'd rather run it on everyone just so it's more predictable.
|
||||||
|
*/
|
||||||
|
object V171_ThreadForeignKeyFix : SignalDatabaseMigration {
|
||||||
|
|
||||||
|
private val TAG = Log.tag(V171_ThreadForeignKeyFix::class.java)
|
||||||
|
|
||||||
|
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
|
val stopwatch = Stopwatch("migration")
|
||||||
|
|
||||||
|
removeDuplicateThreadEntries(db)
|
||||||
|
stopwatch.split("thread-dupes")
|
||||||
|
|
||||||
|
updateThreadTableSchema(db)
|
||||||
|
stopwatch.split("thread-schema")
|
||||||
|
|
||||||
|
stopwatch.stop(TAG)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeDuplicateThreadEntries(db: SQLiteDatabase) {
|
||||||
|
db.rawQuery(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
recipient_id,
|
||||||
|
COUNT(*) AS thread_count
|
||||||
|
FROM thread
|
||||||
|
GROUP BY recipient_id HAVING thread_count > 1
|
||||||
|
""".toSingleLine()
|
||||||
|
).use { cursor ->
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
val recipientId = cursor.requireLong("recipient_id")
|
||||||
|
val count = cursor.requireLong("thread_count")
|
||||||
|
Log.w(TAG, "There were $count threads for RecipientId::$recipientId. Merging.", true)
|
||||||
|
|
||||||
|
val threads: List<ThreadInfo> = getThreadsByRecipientId(db, cursor.requireLong("recipient_id"))
|
||||||
|
mergeThreads(db, threads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getThreadsByRecipientId(db: SQLiteDatabase, recipientId: Long): List<ThreadInfo> {
|
||||||
|
return db.rawQuery("SELECT _id, date FROM thread WHERE recipient_id = ?".trimIndent(), recipientId).readToList { cursor ->
|
||||||
|
ThreadInfo(cursor.requireLong("_id"), cursor.requireLong("date"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mergeThreads(db: SQLiteDatabase, threads: List<ThreadInfo>) {
|
||||||
|
val primaryThread: ThreadInfo = threads.maxByOrNull { it.date }!!
|
||||||
|
val secondaryThreads: List<ThreadInfo> = threads.filterNot { it.id == primaryThread.id }
|
||||||
|
|
||||||
|
secondaryThreads.forEach { secondaryThread ->
|
||||||
|
remapThread(db, primaryThread.id, secondaryThread.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun remapThread(db: SQLiteDatabase, primaryId: Long, secondaryId: Long) {
|
||||||
|
db.update("drafts")
|
||||||
|
.values("thread_id" to primaryId)
|
||||||
|
.where("thread_id = ?", secondaryId)
|
||||||
|
.run()
|
||||||
|
|
||||||
|
db.update("mention")
|
||||||
|
.values("thread_id" to primaryId)
|
||||||
|
.where("thread_id = ?", secondaryId)
|
||||||
|
.run()
|
||||||
|
|
||||||
|
db.update("mms")
|
||||||
|
.values("thread_id" to primaryId)
|
||||||
|
.where("thread_id = ?", secondaryId)
|
||||||
|
.run()
|
||||||
|
|
||||||
|
db.update("sms")
|
||||||
|
.values("thread_id" to primaryId)
|
||||||
|
.where("thread_id = ?", secondaryId)
|
||||||
|
.run()
|
||||||
|
|
||||||
|
db.update("pending_retry_receipts")
|
||||||
|
.values("thread_id" to primaryId)
|
||||||
|
.where("thread_id = ?", secondaryId)
|
||||||
|
.run()
|
||||||
|
|
||||||
|
// We're dealing with threads that exist, so we don't need to remap old_ids
|
||||||
|
|
||||||
|
val count = db.update("remapped_threads")
|
||||||
|
.values("new_id" to primaryId)
|
||||||
|
.where("new_id = ?", secondaryId)
|
||||||
|
.run()
|
||||||
|
Log.w(TAG, "Remapped $count remapped_threads new_ids from $secondaryId to $primaryId", true)
|
||||||
|
|
||||||
|
db.delete("thread")
|
||||||
|
.where("_id = ?", secondaryId)
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateThreadTableSchema(db: SQLiteDatabase) {
|
||||||
|
db.execSQL(
|
||||||
|
"""
|
||||||
|
CREATE TABLE thread_tmp (
|
||||||
|
_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
date INTEGER DEFAULT 0,
|
||||||
|
meaningful_messages INTEGER DEFAULT 0,
|
||||||
|
recipient_id INTEGER NOT NULL UNIQUE REFERENCES recipient (_id) ON DELETE CASCADE,
|
||||||
|
read INTEGER DEFAULT 1,
|
||||||
|
type INTEGER DEFAULT 0,
|
||||||
|
error INTEGER DEFAULT 0,
|
||||||
|
snippet TEXT,
|
||||||
|
snippet_type INTEGER DEFAULT 0,
|
||||||
|
snippet_uri TEXT DEFAULT NULL,
|
||||||
|
snippet_content_type TEXT DEFAULT NULL,
|
||||||
|
snippet_extras TEXT DEFAULT NULL,
|
||||||
|
unread_count INTEGER DEFAULT 0,
|
||||||
|
archived INTEGER DEFAULT 0,
|
||||||
|
status INTEGER DEFAULT 0,
|
||||||
|
delivery_receipt_count INTEGER DEFAULT 0,
|
||||||
|
read_receipt_count INTEGER DEFAULT 0,
|
||||||
|
expires_in INTEGER DEFAULT 0,
|
||||||
|
last_seen INTEGER DEFAULT 0,
|
||||||
|
has_sent INTEGER DEFAULT 0,
|
||||||
|
last_scrolled INTEGER DEFAULT 0,
|
||||||
|
pinned INTEGER DEFAULT 0,
|
||||||
|
unread_self_mention_count INTEGER DEFAULT 0
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
db.execSQL(
|
||||||
|
"""
|
||||||
|
INSERT INTO thread_tmp
|
||||||
|
SELECT
|
||||||
|
_id,
|
||||||
|
date,
|
||||||
|
meaningful_messages,
|
||||||
|
recipient_id,
|
||||||
|
read,
|
||||||
|
type,
|
||||||
|
error,
|
||||||
|
snippet,
|
||||||
|
snippet_type,
|
||||||
|
snippet_uri,
|
||||||
|
snippet_content_type,
|
||||||
|
snippet_extras,
|
||||||
|
unread_count,
|
||||||
|
archived,
|
||||||
|
status,
|
||||||
|
delivery_receipt_count,
|
||||||
|
read_receipt_count,
|
||||||
|
expires_in,
|
||||||
|
last_seen,
|
||||||
|
has_sent,
|
||||||
|
last_scrolled,
|
||||||
|
pinned,
|
||||||
|
unread_self_mention_count
|
||||||
|
FROM thread
|
||||||
|
""".trimMargin()
|
||||||
|
)
|
||||||
|
|
||||||
|
db.execSQL("DROP TABLE thread")
|
||||||
|
db.execSQL("ALTER TABLE thread_tmp RENAME TO thread")
|
||||||
|
|
||||||
|
db.execSQL("CREATE INDEX thread_recipient_id_index ON thread (recipient_id)")
|
||||||
|
db.execSQL("CREATE INDEX archived_count_index ON thread (archived, meaningful_messages)")
|
||||||
|
db.execSQL("CREATE INDEX thread_pinned_index ON thread (pinned)")
|
||||||
|
db.execSQL("CREATE INDEX thread_read ON thread (read)")
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ThreadInfo(val id: Long, val date: Long)
|
||||||
|
}
|
Ładowanie…
Reference in New Issue