mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 04:28:35 +00:00
Fix foreign key constraint issues with backup restore.
This commit is contained in:
@@ -5,6 +5,7 @@ import android.text.TextUtils
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import org.signal.core.util.logging.Log
|
||||
import java.lang.Exception
|
||||
import java.util.LinkedList
|
||||
import java.util.Locale
|
||||
import java.util.stream.Collectors
|
||||
@@ -87,6 +88,27 @@ object SqlUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of all foreign key violations present.
|
||||
* If a [targetTable] is specified, results will be limited to that table specifically.
|
||||
* Otherwise, the check will be performed across all tables.
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun getForeignKeyViolations(db: SupportSQLiteDatabase, targetTable: String? = null): List<ForeignKeyViolation> {
|
||||
val tableString = if (targetTable != null) "($targetTable)" else ""
|
||||
|
||||
return db.query("PRAGMA foreign_key_check$tableString").readToList { cursor ->
|
||||
val table = cursor.requireNonNullString("table")
|
||||
ForeignKeyViolation(
|
||||
table = table,
|
||||
violatingRowId = cursor.requireLongOrNull("rowid"),
|
||||
dependsOnTable = cursor.requireNonNullString("parent"),
|
||||
column = getForeignKeyViolationColumn(db, table, cursor.requireLong("fkid"))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isEmpty(db: SupportSQLiteDatabase, table: String): Boolean {
|
||||
db.query("SELECT COUNT(*) FROM $table", null).use { cursor ->
|
||||
@@ -410,6 +432,21 @@ object SqlUtil {
|
||||
return Query(query, args.toTypedArray())
|
||||
}
|
||||
|
||||
/** Helper that gets the specific column for a foreign key violation */
|
||||
private fun getForeignKeyViolationColumn(db: SupportSQLiteDatabase, table: String, id: Long): String? {
|
||||
try {
|
||||
db.query("PRAGMA foreign_key_list($table)").forEach { cursor ->
|
||||
if (cursor.requireLong("id") == id) {
|
||||
return cursor.requireString("from")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to find violation details for id: $id")
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
class Query(val where: String, val whereArgs: Array<String>) {
|
||||
infix fun and(other: Query): Query {
|
||||
return if (where.isNotEmpty() && other.where.isNotEmpty()) {
|
||||
@@ -422,6 +459,20 @@ object SqlUtil {
|
||||
}
|
||||
}
|
||||
|
||||
data class ForeignKeyViolation(
|
||||
/** The table that declared the REFERENCES clause. */
|
||||
val table: String,
|
||||
|
||||
/** The rowId of the message in [table] that violates the constraint. Will not be present if the table has now rowId. */
|
||||
val violatingRowId: Long?,
|
||||
|
||||
/** The table that [table] has a dependency on. */
|
||||
val dependsOnTable: String,
|
||||
|
||||
/** The column from [table] that has the constraint. A separate query needs to be made to get this, so it's best-effor. */
|
||||
val column: String?
|
||||
)
|
||||
|
||||
enum class CollectionOperator(val sql: String) {
|
||||
IN("IN"),
|
||||
NOT_IN("NOT IN")
|
||||
|
||||
Reference in New Issue
Block a user