I have a database in Android with Room from which I have deleted a column. I was doing the migration, and I saw that it was not as simple as doing a DROP of the deleted column.
Then I have seen that I have to take a series of steps, creating a provisional table that will later be the new table with the deleted column, but the problem is that this table contains a field that is a String Array that I don't know how to declare in SQL.
#Entity(tableName = "recipe_table")
data class RecipesDb(
#PrimaryKey
#ColumnInfo(name = "id")
val id: Long,
#ColumnInfo(name = "name")
val name: String,
#ColumnInfo(name = "category")
val category: List<String>,
#ColumnInfo(name = "isRecommended")
val isRecommended: Boolean,
#ColumnInfo(name = "images")
val images: List<String>,
#ColumnInfo(name = "ingredients")
val ingredients: List<String>,
#ColumnInfo(name = "date")
val date: Long,
#ColumnInfo(name = "time")
val time: Int,
#ColumnInfo(name = "difficult")
val difficult: String,
#ColumnInfo(name = "originalUrl")
val originalURL: String? = null,
#ColumnInfo(name = "author")
val author: String,
#ColumnInfo(name = "siteName")
val siteName: String
)
And now I have removed the ingredients column. I wanted to do something like this:
private val MIGRATION_3_2 = object : Migration(3,2) {
override fun migrate(database: SupportSQLiteDatabase) {
//Drop column isn't supported by SQLite, so the data must manually be moved
with(database) {
execSQL("CREATE TABLE Users_Backup (id INTEGER, name TEXT, PRIMARY KEY (id))")
execSQL("INSERT INTO Users_Backup SELECT id, name FROM Users")
execSQL("DROP TABLE Users")
execSQL("ALTER TABLE Users_Backup RENAME to Users")
}
}
}
But when I declare the new temporary table User_Backup, I have no idea how to specify that one of the fields is an Array. In the end I was able to do it with Room's AutoMigrations and creating an interface, but I would like to know how to do it this way as well.
The simple way is to compile the code (Ctrl+F9) with the changed #Entity annotated classes in the list of entities of the #Database annotation.
Then look at the generated java (visible via the Android View in Android Studio). There will be a class that is the same name as the #Database annotated class but suffixed with _Impl.
In this class there will be a method that is named createAllTables, This includes the SQL that room uses for creating the tables.
Just copy and paste the appropriate SQL and then change the table name, this will not only use the correct type but also apply the correct column constraints that Room expects.
I would suggest
Adding an execSQL("DROP TABLE IF EXISTS the_backup_table_name;") before you create a table (just in case it already exists)
And instead of using execSQL("DROP TABLE Users") to use execSQL("DROP TABLE IF EXISTS the_original_table_name")
Personally I always RENAME the table name of the original, then RENAME the new table and then finally DROP the renamed original.
I would use:-
private val MIGRATION_3_2 = object : Migration(3,2) {
override fun migrate(database: SupportSQLiteDatabase) {
//Drop column isn't supported by SQLite, so the data must manually be moved
with(database) {
execSQL("DROP TABLE IF EXISTS Users_Backup")
execSQL("CREATE TABLE IF NOT EXISTS ....) //<<<<< SEE NOTES BELOW, the SQL MUST BE CHANGED.
execSQL("INSERT INTO Users_Backup SELECT id, name FROM Users")
execSQL("ALTER TABLE Users RENAME TO Old_Users")
execSQL("ALTER TABLE Users_Backup RENAME to Users")
execSQL("DROP TABLE IF EXISTS Old_users")
}
}
}
note .... indicates that the SQL is copied from the generated java and that the table name is changed from Users to Users_Backup
The first line will drop the Uers_backup just in case it happens to exist, it's just a little less likely to fail under unusual circumstances.
Rather than dropping the Users table before the RENAME of the Users_Backup to Users. The 4th execSQL changes the name of the Users table, so should there be an issue with changing the Users_Backup table to be the Users table, then the original Uers table is available as Old_users.
When all has been complted then the original Users table, now named Old_Users is then dropped.
These are all just a little safer/secure.
Related
I have changed a column type from Float to Int, how can I migrate the change so I won't lost old entries, instead just to convert all of them to the Int.
You need to add a Migration that will create the table in it's new form and copy the data from it's old form.
The SQL for the new form can be ascertained by looking at the generated java (visible from the Android View of Android Studio). Look at the class that is named the same as the class that is annotated with #Database but suffixed with _Impl, and then find the SQL in the method named createAllTables
You could then use the following
:-
DROP, just in case the intermediate old table e.g. DROP TABLE IF EXISTS table_old;
RENAME the original table using SQL based upon ALTER TABLE the_table RENAME TO the_table_old;
Create the new table using the SQL as obtained above
Copy the data using SQL based upon INSERT INTO the_table SELECT * FROM the_table_old;
DROP the now defunct old table e.g. DROP TABLE IF EXISTS table_old;;
Demo
As an example where the entity is (was commented out) :-
#Entity(tableName = "jourTable")
class Note(
#ColumnInfo(name = "title") val jourTitle:String,
#ColumnInfo(name = "description") val jourDescription:String,
#ColumnInfo(name = "date") val jourDate:String,
#ColumnInfo(name = "image", typeAffinity = ColumnInfo.BLOB) val jourImage: Bitmap?, //<<<<< will use the TypeConverter
//#ColumnInfo(name = "altImage") val jourAltImage: ByteArray //<<<<< will not use the TypeConverter
#ColumnInfo(name = "altImage") val jourAltImage: Int
) {
#PrimaryKey(autoGenerate = true)var id=0
}
i.e. commented out jourAltImage was ByteArray now to be Int (INTEGER type in SQL)
and the generated java is obtained via :-
The the #Database annotated class (TheDatabase) has :-
#TypeConverters(ImageConverter::class)
#Database(entities = [Note::class], version = 2 /*<<<<<<<<<< INCREASE FROM 1 to 2 (or as required)*/, exportSchema = false)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
#Volatile
private var instance: TheDatabase ? = null
fun getInstance(context: Context): TheDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries()
.addMigrations(MIG_1_2) //<<<<<<<<<< ADD the migration
.build()
}
return instance as TheDatabase
}
/*<<<<<<<<<< The Migration >>>>>>>>>> */
val MIG_1_2 = object: Migration(1,2){
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("DROP TABLE IF EXISTS jourTable_old;")
db.execSQL("ALTER TABLE jourTable RENAME TO jourTable_old ")
/* SQL ON NEXT LINE COPIED FROM GENERATED JAVA */
db.execSQL("CREATE TABLE IF NOT EXISTS `jourTable` (`title` TEXT NOT NULL, `description` TEXT NOT NULL, `date` TEXT NOT NULL, `image` BLOB, `altImage` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)")
db.execSQL("INSERT INTO jourTable SELECT * FROM jourTable_old")
db.execSQL("DROP TABLE IF EXISTS jourTable_old")
}
}
}
}
The when run App Inspection shows (it had 1 row) and the app adds 1 new row when run :-
as cab seen the altimage column is now INTEGER (was BLOB) and the 1 row has been retained.
I'm trying to have a prepopulated database with my Android app but I'm finding with the database inspector that the table is empty. I found it was working before I added a "favourite" column but now it doesn't work (also removing "favourites" still doesn't make it work, so I had changed other things too).
#Entity
#Parcelize
data class Quote (
#PrimaryKey(autoGenerate = true) val id: Int,
val quote: String,
val author: String,
val genre: String,
val favourite: Int
) : Parcelable
#Database(entities = [Quote::class], version = 10)
abstract class QuoteDatabase : RoomDatabase() {
abstract fun quoteDao(): QuoteDao
}
private lateinit var INSTANCE: QuoteDatabase
fun getDatabase(context: Context) : QuoteDatabase {
synchronized(QuoteDatabase::class.java) {
if (!::INSTANCE.isInitialized) {
INSTANCE = Room.databaseBuilder(context.applicationContext,
QuoteDatabase::class.java,
"quotes")
.createFromAsset("quotes.db")
.fallbackToDestructiveMigration()
.build()
}
return INSTANCE
}
}
// Exported Schema
"tableName": "Quote",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `quote` TEXT NOT NULL, `author` TEXT NOT NULL, `genre` TEXT NOT NULL, `favourite` INTEGER NOT NULL)"
It looks like my schema is okay and I had even exported the schema from Room and it looks like it matches my current table schema. My file is also directly in "assets/quotes.db" (doesn't have any subfolders like "database"). The DB is being generated within the emulator from device file inspector. But database inspector is showing nothing. Even when I copy the file from the device and open it with DB Browser there's nothing in there. And of course, my prepopulated database has data in it.
What's going wrong?
I'm new to android room library. I need to migrate a Not Null column to Null,
But room migration only allow ADD or RENAME in ALTER table query. How do execute a column migration query?
#Entity(tableName = "vehicle_detail")
data class VehicleDetailsEntity(
#PrimaryKey(autoGenerate = true)
val vehicleClientId: Long = 0,
val vehicleId: String,
val updatedOn: Date,
val updatedBy: String
)
I need to change table structure into
#Entity(tableName = "vehicle_detail")
data class VehicleDetailsEntity(
#PrimaryKey(autoGenerate = true)
val vehicleClientId: Long = 0,
val vehicleId: String,
val updatedOn: Date?,
val updatedBy: String?
)
java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
You need to run a migration since SQLite doesn't allow column constraint modification.
For that migration you need to create a new temp table and copy all your previous data to it, then delete the old table and rename the temp one to the needed table name.
If you have a scheme directory, you can find your exact creation SQL query which you should copy on your migration (I just figured it out from a scheme of mine and could not be 100% correct):
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
// Create the new table
database.execSQL(
"CREATE TABLE IF NOT EXISTS VehicleDetailsEntityTmp (vehicleId TEXT NOT NULL, updatedOn TEXT, updatedBy TEXT,vehicleClientId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL )"
)
// Copy the data
database.execSQL(
"INSERT INTO VehicleDetailsEntityTmp (vehicleId, updatedOn, updatedBy ,vehicleClientId) SELECT vehicleId, updatedOn, updatedBy ,vehicleClientId FROM VehicleDetailsEntity ")
// Remove the old table
database.execSQL("DROP TABLE VehicleDetailsEntity")
// Change the table name to the correct one
database.execSQL("ALTER TABLE VehicleDetailsEntityTmp RENAME TO VehicleDetailsEntity")
}
}
Issue
In my project I a have a room table named 'content' with a Double attribute 'archivedCount'. In the latest version of the app the attribute archivedCount attribute is re-named to dismissCount, still as type Double.
Android API Level / SQL Version
28 / 3.19
Original Content model
#Entity(tableName = "content")
data class Content(#PrimaryKey var id: String, var archiveCount: Double) : Parcelable {...}
New Content model
#Entity(tableName = "content")
data class Content(#PrimaryKey var id: String, var dismissCount: Double) : Parcelable {...}
Runtime error
java.lang.IllegalStateException: Migration didn't properly handle content(app.coinverse.content.models.Content).
I've inspected the Expected and Found tables the log prints and they appear to be identical.
Attempted Solution
I attempted the complex schema change as outlined by a Google Developer Advocate unsuccessfully in order to modify the name of one attribute / column. Here is a basic version of what I attempted.
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
// Create the new table
database.execSQL("CREATE TABLE content_new (id TEXT, dismissCount REAL, PRIMARY KEY(id))")
// Copy the data
database.execSQL("INSERT INTO content_new (id, dismissCount) SELECT id, archiveCount FROM content")
// Remove the old table
database.execSQL("DROP TABLE content")
// Change the table name to the correct one
database.execSQL("ALTER TABLE content_new RENAME TO content")
}
}
Can't see anything wrong with your implementation, I would suggest that you use a different #Entity class that is not named Content() and try again.
I decided to use Room in my current application.
Find out that there are no type for one column in current schema and Room produce IllegalStateException on migration.
java.lang.IllegalStateException: Migration didn't properly handle item.
Expected:
TableInfo{name='item', columns={optional_modifiers=Column{a_type=Column{name='a_type', type='BLOB', notNull=false, primaryKeyPosition=0}...}
Found:
TableInfo{name='item', columns={optional_modifiers=Column{a_type=Column{name='a_type', type='', notNull=false, primaryKeyPosition=0}...}
Sql script of the table creation:
"create table item ("
" id text primary key," +
" a_type, "
//...
")
Entity class:
#Entity(tableName = "item")
data class Item(
#PrimaryKey
val id: String?,
val a_type: String? // actually I used several types, but none of them is worked
)
Are there any way to resolve this issue?
Sqlite doesn't allow to edit schema. So the only possible way is create the new table with correct columns info, move data to it, delete old table.
Here is the example of the code that I used
database?.execSQL("create table table_name_tmp ( " +
" id text not null primary key"
")")
database?.execSQL("""
insert into table_name_tmp (id)
select id
from table_name
""")
database?.execSQL("drop table table_name")
database?.execSQL("alter table table_name_tmp rename to table_name")
Make modification to your class annotated with #Entity as below.
#Entity(tableName = "item")
data class Item(
#PrimaryKey
val id: String?,
#ColumnInfo(typeAffinity = ColumnInfo.BLOB)
val a_type: String?
)
This will work, because from error it is visible that your old db schema is having name='a_type', type='BLOB', so you need to add #ColumnInfo(typeAffinity = ColumnInfo.BLOB) this will instruct room to consider data type of "a_type" as BLOB.
Recently it comes to my attention that #ColumnInfo also provide "UNDEFINED" as a type Affinity so with that you can now declare your table field without any type.Documentation
please try updated changes in your project.
#Entity(tableName = "item")
data class Item(
#PrimaryKey
val id: String?,
#ColumnInfo(typeAffinity = ColumnInfo.UNDEFINED)
val a_type: String?
)