mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-28 05:35:44 +00:00
Add migration to repair missing FTS triggers.
This commit is contained in:
@@ -120,6 +120,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V260_RemapQuoteAuth
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V261_RemapCallRingers
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V263_InAppPaymentsSubscriberTableRebuild
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V264_FixGroupAddMemberUpdate
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V265_FixFtsTriggers
|
||||
|
||||
/**
|
||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||
@@ -243,10 +244,11 @@ object SignalDatabaseMigrations {
|
||||
261 to V261_RemapCallRingers,
|
||||
// V263 was originally V262, but a typo in the version mapping caused it not to be run.
|
||||
263 to V263_InAppPaymentsSubscriberTableRebuild,
|
||||
264 to V264_FixGroupAddMemberUpdate
|
||||
264 to V264_FixGroupAddMemberUpdate,
|
||||
265 to V265_FixFtsTriggers
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 264
|
||||
const val DATABASE_VERSION = 265
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
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
|
||||
|
||||
/**
|
||||
* We've seen evidence of some users missing certain triggers. This migration checks for that, and if so, will completely tear down and rebuild the FTS.
|
||||
*/
|
||||
@Suppress("ClassName")
|
||||
object V265_FixFtsTriggers : SignalDatabaseMigration {
|
||||
|
||||
private val TAG = Log.tag(V265_FixFtsTriggers::class)
|
||||
|
||||
private const val FTS_TABLE_NAME = "message_fts"
|
||||
|
||||
private val REQUIRED_TRIGGERS = listOf(
|
||||
"message_ai",
|
||||
"message_ad",
|
||||
"message_au"
|
||||
)
|
||||
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
val stopwatch = Stopwatch("migration")
|
||||
|
||||
val hasAllTriggers = REQUIRED_TRIGGERS.all { db.triggerExists(it) }
|
||||
if (hasAllTriggers) {
|
||||
Log.d(TAG, "Already have all triggers, no need for corrective action.")
|
||||
return
|
||||
}
|
||||
stopwatch.split("precheck")
|
||||
|
||||
Log.w(TAG, "We're missing some triggers! Tearing everything down and rebuilding it.")
|
||||
|
||||
try {
|
||||
db.execSQL("DROP TABLE IF EXISTS $FTS_TABLE_NAME")
|
||||
} catch (e: Throwable) {
|
||||
Log.w(TAG, "Failed to drop the message_fts table! Trying a different way.")
|
||||
db.safeDropFtsTable()
|
||||
}
|
||||
|
||||
db.execSQL("DROP TRIGGER IF EXISTS message_ai")
|
||||
db.execSQL("DROP TRIGGER IF EXISTS message_ad")
|
||||
db.execSQL("DROP TRIGGER IF EXISTS message_au")
|
||||
stopwatch.split("drop")
|
||||
|
||||
db.execSQL("""CREATE VIRTUAL TABLE $FTS_TABLE_NAME USING fts5(body, thread_id UNINDEXED, content=message, content_rowid=_id, tokenize = "unicode61 categories 'L* N* Co Sc So'")""")
|
||||
|
||||
db.execSQL("INSERT INTO message_fts(message_fts) VALUES ('rebuild')")
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
CREATE TRIGGER message_ai AFTER INSERT ON message BEGIN
|
||||
INSERT INTO message_fts(rowid, body, thread_id) VALUES (new._id, new.body, new.thread_id);
|
||||
END;
|
||||
"""
|
||||
)
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
CREATE TRIGGER message_ad AFTER DELETE ON message BEGIN
|
||||
INSERT INTO message_fts(message_fts, rowid, body, thread_id) VALUES ('delete', old._id, old.body, old.thread_id);
|
||||
END;
|
||||
"""
|
||||
)
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
CREATE TRIGGER message_au AFTER UPDATE ON message BEGIN
|
||||
INSERT INTO message_fts(message_fts, rowid, body, thread_id) VALUES('delete', old._id, old.body, old.thread_id);
|
||||
INSERT INTO message_fts(rowid, body, thread_id) VALUES (new._id, new.body, new.thread_id);
|
||||
END;
|
||||
"""
|
||||
)
|
||||
|
||||
stopwatch.split("rebuild")
|
||||
stopwatch.stop(TAG)
|
||||
}
|
||||
|
||||
/**
|
||||
* Due to issues we've had in the past, the delete sequence here is very particular. It mimics the "safe drop" process in the SQLite source code
|
||||
* that prevents weird vtable constructor issues when dropping potentially-corrupt tables. https://sqlite.org/src/info/4db9258a78?ln=1549-1592
|
||||
*/
|
||||
private fun SQLiteDatabase.safeDropFtsTable() {
|
||||
if (SqlUtil.tableExists(this, FTS_TABLE_NAME)) {
|
||||
val dataExists = SqlUtil.tableExists(this, "${FTS_TABLE_NAME}_data")
|
||||
val configExists = SqlUtil.tableExists(this, "${FTS_TABLE_NAME}_config")
|
||||
|
||||
if (dataExists) this.execSQL("DELETE FROM ${FTS_TABLE_NAME}_data")
|
||||
if (configExists) this.execSQL("DELETE FROM ${FTS_TABLE_NAME}_config")
|
||||
if (dataExists) this.execSQL("INSERT INTO ${FTS_TABLE_NAME}_data VALUES(10, X'0000000000')")
|
||||
if (configExists) this.execSQL("INSERT INTO ${FTS_TABLE_NAME}_config VALUES('version', 4)")
|
||||
|
||||
this.execSQL("DROP TABLE $FTS_TABLE_NAME")
|
||||
}
|
||||
}
|
||||
|
||||
private fun SQLiteDatabase.triggerExists(tableName: String): Boolean {
|
||||
this.query("SELECT name FROM sqlite_master WHERE type=? AND name=?", arrayOf("trigger", tableName)).use { cursor ->
|
||||
return cursor.moveToFirst()
|
||||
}
|
||||
}
|
||||
|
||||
private fun SQLiteDatabase.tableExists(table: String): Boolean {
|
||||
this.query("SELECT name FROM sqlite_master WHERE type=? AND name=?", arrayOf("table", table)).use { cursor ->
|
||||
return cursor.moveToFirst()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,9 +170,10 @@ public class ApplicationMigrations {
|
||||
static final int SSRE2_CAPABILITY = 126;
|
||||
// static final int FIX_INACTIVE_GROUPS = 127;
|
||||
static final int DUPLICATE_E164_FIX = 128;
|
||||
static final int FTS_TRIGGER_FIX = 129;
|
||||
}
|
||||
|
||||
public static final int CURRENT_VERSION = 128;
|
||||
public static final int CURRENT_VERSION = 129;
|
||||
|
||||
/**
|
||||
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
|
||||
@@ -783,6 +784,10 @@ public class ApplicationMigrations {
|
||||
jobs.put(Version.DUPLICATE_E164_FIX, new DuplicateE164MigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.FTS_TRIGGER_FIX) {
|
||||
jobs.put(Version.FTS_TRIGGER_FIX, new DatabaseMigrationJob());
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user