Fix foreign key constraint issues with backup restore.

This commit is contained in:
Greyson Parrelli
2023-04-25 15:52:09 -04:00
committed by GitHub
parent 0e631508b2
commit a01fb7ff1c
7 changed files with 104 additions and 27 deletions

View File

@@ -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")