Fix table drop order during backup import.

Fixes #12671
This commit is contained in:
Greyson Parrelli
2022-12-12 11:54:05 -05:00
committed by GitHub
parent a6a185004d
commit bd11ed9f17
3 changed files with 239 additions and 23 deletions

View File

@@ -5,11 +5,11 @@ import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
@@ -42,29 +42,24 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Collectors;
public class FullBackupImporter extends FullBackupBase {
@SuppressWarnings("unused")
private static final String TAG = Log.tag(FullBackupImporter.class);
private static final String[] TABLES_TO_DROP_FIRST = {
"distribution_list_member",
"distribution_list",
"message_send_log_recipients",
"msl_recipient",
"msl_message",
"reaction",
"notification_profile_schedule",
"notification_profile_allowed_members",
"story_sends"
};
public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret,
@NonNull SQLiteDatabase db, @NonNull Uri uri, @NonNull String passphrase)
throws IOException
@@ -268,21 +263,69 @@ public class FullBackupImporter extends FullBackupBase {
}
private static void dropAllTables(@NonNull SQLiteDatabase db) {
for (String name : TABLES_TO_DROP_FIRST) {
db.execSQL("DROP TABLE IF EXISTS " + name);
for (String trigger : SqlUtil.getAllTriggers(db)) {
Log.i(TAG, "Dropping trigger: " + trigger);
db.execSQL("DROP TRIGGER IF EXISTS " + trigger);
}
for (String table : getTablesToDropInOrder(db)) {
Log.i(TAG, "Dropping table: " + table);
db.execSQL("DROP TABLE IF EXISTS " + table);
}
}
/**
* Returns the list of tables we should drop, in the order they should be dropped in.
* The order is chosen to ensure we won't violate any foreign key constraints when we import them.
*/
private static List<String> getTablesToDropInOrder(@NonNull SQLiteDatabase input) {
List<String> tables = SqlUtil.getAllTables(input)
.stream()
.filter(table -> !table.startsWith("sqlite_"))
.sorted()
.collect(Collectors.toList());
Map<String, Set<String>> dependsOn = new LinkedHashMap<>();
for (String table : tables) {
dependsOn.put(table, SqlUtil.getForeignKeyDependencies(input, table));
}
try (Cursor cursor = db.rawQuery("SELECT name, type FROM sqlite_master", null)) {
while (cursor != null && cursor.moveToNext()) {
String name = cursor.getString(0);
String type = cursor.getString(1);
for (String table : tables) {
Set<String> dependsOnTable = dependsOn.keySet().stream().filter(t -> dependsOn.get(t).contains(table)).collect(Collectors.toSet());
Log.i(TAG, "Tables that depend on " + table + ": " + dependsOnTable);
}
if ("table".equals(type) && !name.startsWith("sqlite_")) {
Log.i(TAG, "Dropping table: " + name);
db.execSQL("DROP TABLE IF EXISTS " + name);
}
return computeTableDropOrder(dependsOn);
}
@VisibleForTesting
static List<String> computeTableDropOrder(@NonNull Map<String, Set<String>> dependsOn) {
List<String> rootNodes = dependsOn.keySet()
.stream()
.filter(table -> {
boolean nothingDependsOnIt = dependsOn.values().stream().noneMatch(it -> it.contains(table));
return nothingDependsOnIt;
})
.sorted()
.collect(Collectors.toList());
LinkedHashSet<String> dropOrder = new LinkedHashSet<>();
Queue<String> processOrder = new LinkedList<>(rootNodes);
while (!processOrder.isEmpty()) {
String head = processOrder.remove();
dropOrder.remove(head);
dropOrder.add(head);
Set<String> dependencies = dependsOn.get(head);
if (dependencies != null) {
processOrder.addAll(dependencies);
}
}
return new ArrayList<>(dropOrder);
}
public static class DatabaseDowngradeException extends IOException {