Проблема миграции после копирования таблицы, как копировать также индексы и ForeignKeys? - PullRequest
1 голос
/ 11 октября 2019

Мне нужно удалить некоторые столбцы из очень большой таблицы roomDB (SQLite).

Из того, что я прочитал, я не могу удалить столбцы из существующей таблицы. Для этого мне нужно создатьновую таблицу и скопируйте нужные столбцы (кроме тех, которые мы хотим удалить)

Мне удается удалить эти столбцы, скопировав в новую таблицу, но миграция не работает, потому что foreignKeys и indexes arae отсутствует, каков чистый синтаксис для этого?

это метод, который я использую при миграции для удаления некоторых столбцов:

 private static void deleteColumns(SupportSQLiteDatabase database, String tableName, List<String> columnsToRemove){
    List<String> columnNames = new ArrayList<>();
    List<String> columnNamesWithType = new ArrayList<>();
    List<String> primaryKeys = new ArrayList<>();
    String query = "pragma table_info(" + tableName + ");";

    Cursor cursor = database.query(query);
    while (cursor.moveToNext()){
        String columnName = cursor.getString(cursor.getColumnIndex("name"));

        if (columnsToRemove.contains(columnName)){
            continue;
        }

        String columnType = cursor.getString(cursor.getColumnIndex("type"));
        boolean isNotNull = cursor.getInt(cursor.getColumnIndex("notnull")) == 1;
        boolean isPk = cursor.getInt(cursor.getColumnIndex("pk")) == 1;

        columnNames.add(columnName);
        String tmp = "`" + columnName + "` " + columnType + " ";
        if (isNotNull){
            tmp += " NOT NULL ";
        }

        int defaultValueType = cursor.getType(cursor.getColumnIndex("dflt_value"));
        if (defaultValueType == Cursor.FIELD_TYPE_STRING){
            tmp += " DEFAULT " + "\"" + cursor.getString(cursor.getColumnIndex("dflt_value")) + "\" ";
        }else if(defaultValueType == Cursor.FIELD_TYPE_INTEGER){
            tmp += " DEFAULT " + cursor.getInt(cursor.getColumnIndex("dflt_value")) + " ";
        }else if (defaultValueType == Cursor.FIELD_TYPE_FLOAT){
            tmp += " DEFAULT " + cursor.getFloat(cursor.getColumnIndex("dflt_value")) + " ";
        }
        columnNamesWithType.add(tmp);
        if (isPk){
            primaryKeys.add("`" + columnName + "`");
        }
    }
    cursor.close();

    String columnNamesSeparated = TextUtils.join(", ", columnNames);
    if (primaryKeys.size() > 0){
        columnNamesWithType.add("PRIMARY KEY("+ TextUtils.join(", ", primaryKeys) +")");
    }
    String columnNamesWithTypeSeparated = TextUtils.join(", ", columnNamesWithType);

    database.beginTransaction();
    try {
        database.execSQL("ALTER TABLE " + tableName + " RENAME TO " + tableName + "_old;");
        database.execSQL("CREATE TABLE " + tableName + " (" + columnNamesWithTypeSeparated + ") + FOREIGN KEY +(trackartist) REFERENCES artist(artistid) ;" );
        database.execSQL("INSERT INTO " + tableName + " (" + columnNamesSeparated + ") SELECT "
                + columnNamesSeparated + " FROM " + tableName + "_old;");
        database.execSQL("DROP TABLE " + tableName + "_old;");
        database.setTransactionSuccessful();
    }finally {
        database.endTransaction();
    }
}

1 Ответ

0 голосов
/ 12 октября 2019

Я не могу скопировать ForeignKeys и индексы

Для Foreign Keys генерация их может быть основана на выводе из PRAGMA foreign_keys_list(the_table), который производит вывод, такой как: -

enter image description here

Для указывает , вы можете основать генерациюих на выходе из PRAGMA index_list(the_table), чтобы получить имена индексов и проверить ограничение UNIQUE.

enter image description here

В качестве имени индекса вы можете использовать PRAGMA index_info(the_index_name)

enter image description here

Например, будет работать следующая адаптация вашего кода (КОММЕНТАРИИ относительно ограничений): -

private static void deleteColumns(SupportSQLiteDatabase database, String tableName, List<String> columnsToRemove){
    List<String> columnNames = new ArrayList<>();
    List<String> columnNamesWithType = new ArrayList<>();
    List<String> primaryKeys = new ArrayList<>();

    String query = "pragma table_info(" + tableName + ");";
    Cursor cursor = database.query(query);
    while (cursor.moveToNext()){
        String columnName = cursor.getString(cursor.getColumnIndex("name"));

        if (columnsToRemove.contains(columnName)){
            continue;
        }

        String columnType = cursor.getString(cursor.getColumnIndex("type"));
        boolean isNotNull = cursor.getInt(cursor.getColumnIndex("notnull")) == 1;
        boolean isPk = cursor.getInt(cursor.getColumnIndex("pk")) == 1;

        columnNames.add(columnName);
        String tmp = "`" + columnName + "` " + columnType + " ";
        if (isNotNull){
            tmp += " NOT NULL ";
        }

        int defaultValueType = cursor.getType(cursor.getColumnIndex("dflt_value"));
        if (defaultValueType == Cursor.FIELD_TYPE_STRING){
            tmp += " DEFAULT " + "\"" + cursor.getString(cursor.getColumnIndex("dflt_value")) + "\" ";
        }else if(defaultValueType == Cursor.FIELD_TYPE_INTEGER){
            tmp += " DEFAULT " + cursor.getInt(cursor.getColumnIndex("dflt_value")) + " ";
        }else if (defaultValueType == Cursor.FIELD_TYPE_FLOAT){
            tmp += " DEFAULT " + cursor.getFloat(cursor.getColumnIndex("dflt_value")) + " ";
        }
        columnNamesWithType.add(tmp);
        if (isPk){
            primaryKeys.add("`" + columnName + "`");
        }
    }

    //<<<<<<<<<< GET THE FOREIGN KEYS SQL FOR THE TABLE >>>>>>>>>>
    //!!!!!WARNING!!!!! DOES NOT ENCLOSE COLUMN NAMES AS THE COULD BE CSV LIST of columns
    //                  (should split and enclose them)
    //!!!!!WARNING!!!!! As information gathered is from the ORIGINAL table there may be issues
    //                  columns are skipped
    cursor = database.query("PRAGMA foreign_key_list(" + tableName + ");");
    StringBuilder foreignKeySQL = new StringBuilder();
    while (cursor.moveToNext()) {
        if (isColumnToBeRemoved(cursor.getString(cursor.getColumnIndex("from")),columnsToRemove)) {
            continue;
        }
        foreignKeySQL.append(",FOREIGN KEY (")
                .append(cursor.getString(cursor.getColumnIndex("from")))
                .append(") REFERENCES `")
                .append(cursor.getString(cursor.getColumnIndex("table")))
                .append("`(")
                .append(cursor.getString(cursor.getColumnIndex("to")))
                .append(") ")
                .append(" ON DELETE ")
                .append(cursor.getString(cursor.getColumnIndex("on_delete")))
                .append(" ON UPDATE ").append(cursor.getString(cursor.getColumnIndex("on_update")))
        ;
    }

    //<<<<<<<<<< GET THE INDICES CREATE SQL as an ArrayList<string> >>>>>>>>>>
    //!!!!!WARNING As information gathered is from the ORIGINAL table there may be issues
    ArrayList<String> indicesSQL = new ArrayList<>();
    cursor = database.query("PRAGMA index_list(" + tableName + ")");
    //Cursor cursor2;

    while (cursor.moveToNext()) {
        boolean includesRemovedColumn = false;
        String unique = "";
        if (cursor.getInt(cursor.getColumnIndex("unique")) > 0) {
            unique = " UNIQUE ";
        }
        StringBuilder currentIndex = new StringBuilder().append("CREATE " + unique + " INDEX IF NOT EXISTS `")
                .append(cursor.getString(cursor.getColumnIndex("name")))
                .append("` ON `").append(tableName).append("`(");
        Cursor cursor2 = database.query("PRAGMA index_info(" + cursor.getString(cursor.getColumnIndex("name")) + ")");
        boolean afterFirst = false;
        while (cursor2.moveToNext()) {
            if (isColumnToBeRemoved(cursor2.getString(cursor2.getColumnIndex("name")),columnsToRemove)) {
                includesRemovedColumn = true;
            }
            if (afterFirst) {
                currentIndex.append(",");
            }
            afterFirst = true;
            currentIndex.append("`")
                    .append(cursor2.getString(cursor2.getColumnIndex("name"))).append("`");
        }
        cursor2.close();
        currentIndex.append(");");
        if (!includesRemovedColumn) {
            indicesSQL.add(currentIndex.toString());
        }
    }
    cursor.close();


    String columnNamesSeparated = TextUtils.join(", ", columnNames);
    if (primaryKeys.size() > 0){
        columnNamesWithType.add("PRIMARY KEY("+ TextUtils.join(", ", primaryKeys) +")");
    }
    String columnNamesWithTypeSeparated = TextUtils.join(", ", columnNamesWithType);

    //<<<<<<<<<< FOR LOGGING/ CHECKING >>>>>>>>>
    String createTableSQL = "CREATE TABLE " + tableName + " (" + columnNamesWithTypeSeparated + foreignKeySQL + ")";
    String insertSQL = "INSERT INTO " + tableName + " (" + columnNamesSeparated + ") SELECT "
            + columnNamesSeparated + " FROM " + tableName + "_old;";
    String alterTableSQL = "ALTER TABLE " + tableName + " RENAME TO " + tableName + "_old;";
    String dropTableSQL = "DROP TABLE " + tableName + "_old;";
    Log.d("ALTERSQL",alterTableSQL);
    Log.d("CREATESQL",createTableSQL);
    Log.d("INSERTSQL",insertSQL);
    Log.d("DROPSQL",dropTableSQL);
    for (String ixsql: indicesSQL) {
        Log.d("CREATEINDEXSQL",ixsql);
    }
    database.beginTransaction();
    try {
        database.execSQL("ALTER TABLE " + tableName + " RENAME TO " + tableName + "_old;");
        //database.execSQL("CREATE TABLE " + tableName + " (" + columnNamesWithTypeSeparated + ") + FOREIGN KEY +(trackartist) REFERENCES artist(artistid) ;" );
        database.execSQL("CREATE TABLE " + tableName + " (" + columnNamesWithTypeSeparated + foreignKeySQL + ")" );
        database.execSQL("INSERT INTO " + tableName + " (" + columnNamesSeparated + ") SELECT "
                + columnNamesSeparated + " FROM " + tableName + "_old;");
        database.execSQL("DROP TABLE " + tableName + "_old;");
        for (String ixsql: indicesSQL) {
            database.execSQL(ixsql);
        }
        database.setTransactionSuccessful();
    }finally {
        database.endTransaction();
    }
}

//<<<<<<<<<< FOR CHECKING IF COLUMN EXISTS >>>>>>>>>>
private static boolean isColumnToBeRemoved(String column, List<String> columnsToRemove) {
    for (String s: columnsToRemove) {
        if (s.toLowerCase().equals(column.toLowerCase())) return true;
    }
    return false;
}

Альтернативный подход, который может бытьпроще, чем пытаться удовлетворить все перестановки, было бы

  • a) изменить сущности и затем
  • b) извлечь SQL из сгенерированного кода из _impl @ Database. Например, вышеупомянутое было успешно проверено на простом удалении из 1 столбца, с двумя FK и индексом, покрывающим оба отображенных столбца, при этом можно было использовать следующие 10 *

: -

  public void createAllTables(SupportSQLiteDatabase _db) {
    _db.execSQL("CREATE TABLE IF NOT EXISTS `device_item` (`id` INTEGER, `initial` TEXT, `added1` INTEGER NOT NULL DEFAULT 0, `added2` TEXT DEFAULT '', PRIMARY KEY(`id`))");
    _db.execSQL("CREATE TABLE IF NOT EXISTS `table1` (`id` INTEGER, `name` TEXT, `mapToTable2` INTEGER NOT NULL, `mapToTable3` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`mapToTable2`) REFERENCES `table2`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`mapToTable3`) REFERENCES `table3`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )");
    _db.execSQL("CREATE INDEX IF NOT EXISTS `index_table1_mapToTable2_mapToTable3` ON `table1` (`mapToTable2`, `mapToTable3`)");
    _db.execSQL("CREATE TABLE IF NOT EXISTS `table2` (`id` INTEGER, `nameOfT2` TEXT, `anotherNameOfT2` TEXT, PRIMARY KEY(`id`))");
    _db.execSQL("CREATE TABLE IF NOT EXISTS `table3` (`id` INTEGER, `nameOfT3` TEXT, `anotherNameOfT3` TEXT, PRIMARY KEY(`id`))");
    _db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
    _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2177a2b52b96e912c0af8db6e8cad3d2')");
  }
  • как написан SQL для вас. это просто вопрос создания ALTER, INSERT AND DROP SQL.

Вышеприведенное сравнение сравнивается с Logged SQL при выполнении вышеуказанного: -

2019-10-12 15:01:34.242 D/ALTERSQL: ALTER TABLE table1 RENAME TO table1_old;
2019-10-12 15:01:34.242 D/CREATESQL: CREATE TABLE table1 (`id` INTEGER , `name` TEXT , `mapToTable2` INTEGER  NOT NULL , `mapToTable3` INTEGER  NOT NULL , PRIMARY KEY(`id`),FOREIGN KEY (mapToTable3) REFERENCES `table3`(id)  ON DELETE NO ACTION ON UPDATE NO ACTION,FOREIGN KEY (mapToTable2) REFERENCES `table2`(id)  ON DELETE NO ACTION ON UPDATE NO ACTION)
2019-10-12 15:01:34.242 D/INSERTSQL: INSERT INTO table1 (id, name, mapToTable2, mapToTable3) SELECT id, name, mapToTable2, mapToTable3 FROM table1_old;
2019-10-12 15:01:34.242 D/DROPSQL: DROP TABLE table1_old;
2019-10-12 15:01:34.242 D/CREATEINDEXSQL: CREATE  INDEX IF NOT EXISTS `index_table1_mapToTable2_mapToTable3` ON `table1`(`mapToTable2`,`mapToTable3`);

Disclaimers

Вышеуказанное не подходит для всех ситуаций, оно предназначено исключительно как демонстрация основных приемов. Код содержит некоторые комментарии относительно ограничений. Однако существуют и другие соображения, например, частичные индексы и предложение WHERE, FTS и столбец MATCH для FK.

...