Я пытался перенести свое приложение на Room
. Я борюсь с конкретной таблицей, которую нельзя перенести напрямую из-за того, как она была создана.
Поля были созданы с типом данных BOOL
и BYTE
вместо INTEGER
.
Я уже не пытался:
typeAffinity
как UNDEFINED
в @ColumnInfo
моей сущности, которая имеет сходство = 1Мое databaseSQL
творческое предложение:
CREATE TABLE IF NOT EXISTS myTable (_id INTEGER PRIMARY KEY AUTOINCREMENT,
my_first_field BOOL NOT NULL DEFAULT 0,
my_second_field BYTE NOT NULL DEFAULT 0)
Моя сущность:
@Entity(tableName = "myTable")
data class MyTable(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id")
var id: Int,
@ColumnInfo(name = "my_first_field")
var myFirstField: Boolean = false,
@ColumnInfo(name = "my_second_field")
var mySecondField: Byte = false
)
Ошибка, которую я постоянно получаю:
Expected:
TableInfo{name='my_table', columns = {_id=Column{name='_id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1}, my_first_field=Column{name='my_first_field', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0}, my_second_field=Column{name='my_second_field', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0}}, foreignKeys=[], indices=[]}
Found:
TableInfo{name='my_table', columns = {_id=Column{name='_id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1}, my_first_field=Column{name='my_first_field', type='BOOL', affinity='1', notNull=true, primaryKeyPosition=0}, my_second_field=Column{name='my_second_field', type='BYTE', affinity='1', notNull=true, primaryKeyPosition=0}}, foreignKeys=[], indices=[]}
Есть ли способ сделать прямо вперед, не создавая стратегию миграции?
@mohammadRezaAbiri, какой еще код вы считаете полезным/необходимым?
Sqlite не имеет типов «логический» или «байтовый»… Используйте целое число. sqlite.org/datatype3.html
Я знаю, что SQLITE не имеет логического или байтового значения. На самом деле, если база данных новая, то этой ошибки не происходит, потому что поле базы данных инициализируется как INTEGER, а не как BYTE или BOOL. Мой вопрос в том, как я могу избежать уничтожения своих таблиц и скопировать информацию в новую таблицу.
Я думаю, вы могли бы, до, создающий базу данных комнат: -
Проверьте, не нужно ли что-то сделать, например. используя :-
SELECT count() FROM sqlite_master WHERE name = 'myTable' AND instr(sql,' BOOL ') AND instr(sql,' BYTE ');
а потом проверяем результат.
Если он равен 0, больше ничего не делайте (хотя на всякий случай вы можете использовать DROP TABLE IF EXISTS oldmyTable только тогда, когда он равен 0).
ТОЛЬКО Если приведенное выше возвращает 1, то: -
удалите переименованную исходную таблицу (см. ниже, а также выше) на всякий случай, если она существует: -
DROP TABLE IF EXISTS oldmyTable;
определить другую таблицу, используя
CREATE TABLE IF NOT EXISTS myOtherTable (_id INTEGER PRIMARY KEY AUTOINCREMENT,
my_first_field INTEGER NOT NULL DEFAULT 0,
my_second_field INTEGER NOT NULL DEFAULT 0)
то есть схема ожидал
заполнить новую таблицу, используя
INSERT INTO myOtherTable SELECT * FROM myTable;
переименуйте mytable, используя: -
ALTER TABLE mytable RENAME TO oldmyTable;
переименуйте myOtherTable, используя исходное имя: -
ALTER TABLE myOtherTable RENAME TO mytable;
удалить переименованную исходную таблицу (очевидно, только при проверке): -
DROP TABLE IF EXISTS oldmyTable;
Конечным результатом является то, что таблица должна быть такой, как ожидал.
Что касается комментария: -
Problem is that I have like 16-20 tables to migrate.
Вы можете использовать что-то вроде: -
public static int preMigrateAdjustment(SQLiteDatabase mDB) {
String original_rename_prefix = "old";
String tempname_suffix = "temp";
String newsql_column = "newsql";
String[] columns = new String[]{
"name",
"replace(replace(sql,' BOOL ',' INTEGER '),' BYTE ',' INTEGER ') AS " + newsql_column
};
int count_done = 0;
String whereclause = "name LIKE('" +
original_rename_prefix +
"%') AND type = 'table'";
Cursor csr = mDB.query("sqlite_master",null,whereclause,null,null,null,null);
while (csr.moveToNext()) {
mDB.execSQL("DROP TABLE IF EXISTS " + csr.getString(csr.getColumnIndex("name")));
}
whereclause = "type = 'table' AND (instr(sql,' BOOL ') OR instr(sql,' BYTE '))";
csr = mDB.query(
"sqlite_master",
columns,
whereclause,
null,null,null,null
);
while (csr.moveToNext()) {
String base_table_name = csr.getString(csr.getColumnIndex("name"));
String newsql = csr.getString(csr.getColumnIndex(newsql_column));
String temp_table_name = base_table_name + tempname_suffix;
String renamed_table_name = original_rename_prefix+base_table_name;
mDB.execSQL(newsql.replace(base_table_name,temp_table_name));
mDB.execSQL("INSERT INTO " + temp_table_name + " SELECT * FROM " + base_table_name);
mDB.execSQL("ALTER TABLE " + base_table_name + " RENAME TO " + renamed_table_name);
mDB.execSQL("ALTER TABLE " + temp_table_name + " RENAME TO " + base_table_name);
count_done++;
}
whereclause = "name LIKE('" +
original_rename_prefix +
"%') AND type = 'table'";
csr = mDB.query("sqlite_master",null,whereclause,null,null,null,null);
while (csr.moveToNext()) {
mDB.execSQL("DROP TABLE IF EXISTS " + csr.getString(csr.getColumnIndex("name")));
}
csr.close();
return count_done;
}
Глядя на это и фактически тестируя (в данном случае с использованием 5 таблиц) с идентичной схемой после разрешения типов BOOL BYTE, в этом кодировании выявляется дополнительная проблема.
_id INTEGER PRIMARY KEY AUTOINCREMENT
приводит к ненуль = ложь, в то время как кодирование
@PrimaryKey(autoGenerate = true)
private long _id;
приводит к ненуль = истина
Например, быстрое исправление, которое предполагает, что AUTOINCREMENT NOT NULL
не закодировано, строка в preMigrateAdjustment была изменена с: -
mDB.execSQL((newsql.replace(base_table_name,temp_table_name)));
к :-
mDB.execSQL((newsql.replace(base_table_name,temp_table_name)).replace("AUTOINCREMENT","AUTOINCREMENT NOT NULL"));
Создание и заполнение старых таблиц выполняется в помощнике по базе данных Исходный DBHelper.java:
public class OriginalDBHelper extends SQLiteOpenHelper {
public static final String DBNAME = "mydb";
public static final int DBVERSION = 1;
int tables_to_create = 5; //<<<<<<<<<< 5 sets of tables
SQLiteDatabase mDB;
public OriginalDBHelper(Context context) {
super(context, DBNAME, null, DBVERSION);
mDB = this.getWritableDatabase();
}
@Override
public void onCreate(SQLiteDatabase db) {
for (int i=0;i < tables_to_create;i++) {
db.execSQL("CREATE TABLE IF NOT EXISTS myTable" + String.valueOf(i) + "X (_id INTEGER PRIMARY KEY AUTOINCREMENT,\n" +
" my_first_field BOOL NOT NULL DEFAULT 0,\n" +
" my_second_field BYTE NOT NULL DEFAULT 0)"
);
db.execSQL("INSERT INTO myTable" + String.valueOf(i) + "X (my_first_field,my_second_field) VALUES(0,0),(1,0),(1,1),(0,1)");
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
т.е. настроить схему в соответствии с комнатой) PreMigrationAdjustment.java
public class PreMigrationAdjustment {
public static int preMigrateAdjustment(SQLiteDatabase mDB) {
String original_rename_prefix = "old";
String tempname_suffix = "temp";
String newsql_column = "newsql";
String[] columns = new String[]{
"name",
"replace(replace(sql,' BOOL ',' INTEGER '),' BYTE ',' INTEGER ') AS " + newsql_column
};
int count_done = 0;
String whereclause = "name LIKE('" +
original_rename_prefix +
"%') AND type = 'table'";
Cursor csr = mDB.query("sqlite_master",null,whereclause,null,null,null,null);
while (csr.moveToNext()) {
mDB.execSQL("DROP TABLE IF EXISTS " + csr.getString(csr.getColumnIndex("name")));
}
whereclause = "type = 'table' AND (instr(sql,' BOOL ') OR instr(sql,' BYTE '))";
csr = mDB.query(
"sqlite_master",
columns,
whereclause,
null,null,null,null
);
while (csr.moveToNext()) {
String base_table_name = csr.getString(csr.getColumnIndex("name"));
String newsql = csr.getString(csr.getColumnIndex(newsql_column));
String temp_table_name = base_table_name + tempname_suffix;
String renamed_table_name = original_rename_prefix+base_table_name;
mDB.execSQL((newsql.replace(base_table_name,temp_table_name)).replace("AUTOINCREMENT","AUTOINCREMENT NOT NULL"));
//mDB.execSQL((newsql.replace(base_table_name,temp_table_name)));
mDB.execSQL("INSERT INTO " + temp_table_name + " SELECT * FROM " + base_table_name);
mDB.execSQL("ALTER TABLE " + base_table_name + " RENAME TO " + renamed_table_name);
mDB.execSQL("ALTER TABLE " + temp_table_name + " RENAME TO " + base_table_name);
count_done++;
}
whereclause = "name LIKE('" +
original_rename_prefix +
"%') AND type = 'table'";
csr = mDB.query("sqlite_master",null,whereclause,null,null,null,null);
while (csr.moveToNext()) {
mDB.execSQL("DROP TABLE IF EXISTS " + csr.getString(csr.getColumnIndex("name")));
}
csr.close();
return count_done;
}
}
только 1 из 5 показанных для краткости, т.е. myTable0X.java
Очевидно, что они должны быть тщательно написаны, чтобы соответствовать таблицам перед комнатой.
@Entity()
public class myTable0X {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id")
private long id;
@ColumnInfo(name = "my_first_field")
private boolean my_first_field;
@ColumnInfo(name = "my_second_field")
private boolean my_second_field;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public boolean isMy_first_field() {
return my_first_field;
}
public void setMy_first_field(boolean my_first_field) {
this.my_first_field = my_first_field;
}
public boolean isMy_second_field() {
return my_second_field;
}
public void setMy_second_field(boolean my_second_field) {
this.my_second_field = my_second_field;
}
}
@Dao
public interface DAOmyTablex {
@Query("SELECT * FROM myTable0X")
List<myTable0X> getAllFrommyTable0();
@Query("SELECT * FROM myTable1X")
List<myTable1X> getAllFrommyTable1();
@Query("SELECT * FROM myTable2X")
List<myTable2X> getAllFrommyTable2();
@Query("SELECT * FROM myTable3X")
List<myTable3X> getAllFrommyTable3();
@Query("SELECT * FROM myTable4X")
List<myTable4X> getAllFrommyTable4();
@Insert
long[] insertAll(myTable0X... myTable0XES);
@Insert
long[] insertAll(myTable1X... myTable1XES);
@Insert
long[] insertAll(myTable2X... myTable2XES);
@Insert
long[] insertAll(myTable3X... myTable3XES);
@Insert
long[] insertAll(myTable4X... myTable4XES);
@Delete
int delete(myTable0X mytable0X);
@Delete
int delete(myTable1X mytable1X);
@Delete
int delete(myTable2X mytable2X);
@Delete
int delete(myTable3X mytable3X);
@Delete
int delete(myTable4X mytable4X);
}
@Database(entities = {myTable0X.class, myTable1X.class, myTable2X.class, myTable3X.class, myTable4X.class},version = 2)
public abstract class mydb extends RoomDatabase {
public abstract DAOmyTablex dbDAO();
}
Он состоит из 3 основных этапов
Когда приложение запускается, оно автоматически выполняет этапы 1 и 2, была добавлена кнопка, при нажатии которой затем выполняется этап 3 (только один раз).
Наконец, данные извлекаются из таблиц (это фактически открывает базу данных Room) и данные из одной из таблиц выводятся в журнал.
public class MainActivity extends AppCompatActivity {
OriginalDBHelper mDBHlpr;
Button mGo;
mydb mMyDB;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mGo = this.findViewById(R.id.go);
mGo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
goForIt();
}
});
mDBHlpr = new OriginalDBHelper(this);
Log.d("STAGE1","The original tables");
dumpAllTables();
Log.d("STAGE2", "Initiaing pre-mirgration run.");
Log.d("STAGE2 A RESULT",
String.valueOf(
PreMigrationAdjustment.preMigrateAdjustment(mDBHlpr.getWritableDatabase()
)
) + " tables converted."
); //<<<<<<<<<< CONVERT THE TABLES
Log.d("STAGE2 B","Dumping adjusted tables");
dumpAllTables();
Log.d("STAGE2 C","Second run Cleanup");
Log.d("STAGE2 DRESULT",
String.valueOf(
PreMigrationAdjustment.preMigrateAdjustment(mDBHlpr.getWritableDatabase()
)
) + " tables converted."
); //<<<<<<<<<< CONVERT THE TABLES
dumpAllTables();
Log.d("STAGE3","Handing over to ROOM (when button is clicked)");
}
private void goForIt() {
if (mMyDB != null) return;
mMyDB = Room.databaseBuilder(this,mydb.class,OriginalDBHelper.DBNAME).addMigrations(MIGRATION_1_2).allowMainThreadQueries().build();
List<myTable0X> mt0 = mMyDB.dbDAO().getAllFrommyTable0();
List<myTable1X> mt1 = mMyDB.dbDAO().getAllFrommyTable1();
List<myTable2X> mt2 = mMyDB.dbDAO().getAllFrommyTable2();
List<myTable3X> mt3 = mMyDB.dbDAO().getAllFrommyTable3();
List<myTable4X> mt4 = mMyDB.dbDAO().getAllFrommyTable4();
for (myTable0X mt: mt0) {
Log.d("THIS_MT","ID is " + String.valueOf(mt.getId()) + " FIELD1 is " + String.valueOf(mt.isMy_first_field()) + " FIELD2 is " + String.valueOf(mt.isMy_second_field()));
}
// etc.......
}
private void dumpAllTables() {
SQLiteDatabase db = mDBHlpr.getWritableDatabase();
Cursor c1 = db.query("sqlite_master",null,"type = 'table'",null,null,null,null);
while (c1.moveToNext()) {
Log.d("TABLEINFO","Dmuping Data for Table " + c1.getString(c1.getColumnIndex("name")));
Cursor c2 = db.query(c1.getString(c1.getColumnIndex("name")),null,null,null,null,null,null);
DatabaseUtils.dumpCursor(c2);
c2.close();
}
c1.close();
}
public final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
/**NOTES
//Tried the pre-migration here BUT SQLiteDatabaseLockedException: database is locked (code 5 SQLITE_BUSY)
//Cannot use SupportSQLiteDatabase as that locks out access to sqlite_master
//PreMigrationAdjustment.preMigrateAdjustment(mDBHlpr.getWritableDatabase()); //Initial run
//PreMigrationAdjustment.preMigrateAdjustment(mDBHlpr.getWritableDatabase()); //Cleanup run
*/
}
};
}
Результат (только вывод STAGE????): -
2019-05-19 13:18:12.227 D/STAGE1: The original tables
2019-05-19 13:18:12.244 D/STAGE2: Initiaing pre-mirgration run.
2019-05-19 13:18:12.281 D/STAGE2 A RESULT: 5 tables converted.
2019-05-19 13:18:12.281 D/STAGE2 B: Dumping adjusted tables
2019-05-19 13:18:12.303 D/STAGE2 C: Second run Cleanup
2019-05-19 13:18:12.304 D/STAGE2 DRESULT: 0 tables converted.
2019-05-19 13:18:12.331 D/STAGE3: Handing over to ROOM (when button is clicked)
Финальные ряды: -
2019-05-19 13:20:03.090 D/THIS_MT: ID is 1 FIELD1 is false FIELD2 is false
2019-05-19 13:20:03.090 D/THIS_MT: ID is 2 FIELD1 is true FIELD2 is false
2019-05-19 13:20:03.090 D/THIS_MT: ID is 3 FIELD1 is true FIELD2 is true
2019-05-19 13:20:03.090 D/THIS_MT: ID is 4 FIELD1 is false FIELD2 is true
Это фактический способ, которым я управляю этим, поскольку кажется, что нет другого способа, кроме как перенести его. Проблема в том, что база данных уже создана и заполнена. Проблема в том, что мне нужно перенести 16-20 таблиц. МНОГО. Если нет лучшего ответа или какого-либо способа сделать это прямо, я проголосую за это и выберу его как правильный ответ.
@AndresOller Вы можете использовать что-то вроде SELECT name, replace(replace(sql,' BOOL ',' INTEGER '),' BYTE ',' INTEGER ') AS newsql FROM sqlite_master WHERE instr(sql,' BOOL ') OR instr(sql,' BYTE ');
для управления процессом с несколькими таблицами. Итак, для каждой строки (ничего не делайте, если нет) у вас есть имя таблицы и скорректированный SQL.
добавьте больше кода, пожалуйста!