Reputation: 535
My Room Database is as follows
@Database(entities = [UserEntity::class], version = 6)
abstract class RoomDB : RoomDatabase() {
abstract fun getDao(): RoomDao
companion object {
private var INSTANCE: RoomDB? = null
val MIGRATION_5_6 = object : Migration(5, 6) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"CREATE TABLE my_table_new (" +
"id INTEGER NOT NULL, " +
"name TEXT, " +
"age INTEGER NOT NULL, " +
"PRIMARY KEY(id))"
)
database.execSQL("INSERT INTO my_table_new (id, name, age) SELECT id, name, 0 FROM my_table")
database.execSQL("DROP TABLE my_table")
database.execSQL("ALTER TABLE my_table_new RENAME TO my_table")
}
}
fun getInstance(context: Context): RoomDB {
if (INSTANCE == null) {
synchronized(RoomDB::class) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
RoomDB::class.java,
"user.db"
)
.addMigrations(MIGRATION_5_6)
.build()
}
}
return INSTANCE!!
}
}
}
The previous version was 5, this time the upgraded version is 6. There was no age
column in version 5, but it was newly added in version 6. So if you look at the INSERT INTO
statement, you can see that the number 0 is forcibly inserted. As you can see from the code, we have also written the MIGRATION code. Dao is:
@Dao
interface RoomDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(entity: UserEntity)
@Delete
fun delete(entity: UserEntity)
@Query("SELECT * FROM my_table")
fun getUser(): List<UserEntity>
@Query("DELETE FROM my_table")
fun deleteAll()
}
Entity looks like this:
@Entity(tableName = "my_table")
data class UserEntity(
@ColumnInfo(name = "id")
@PrimaryKey var id: Int,
@ColumnInfo(name = "name")
var name: String,
@ColumnInfo(name = "age")
var age: Int
)
When I call getUser() here, I get the following Exception.
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.
How on earth should I upgrade the Room version? Is there anything I am doing wrong?
Upvotes: 1
Views: 991
Reputation: 57043
I've just tested your code and the migration runs BUT the schema for the new table is incorrect and you will get IllegalStateException:Migration didn't properly handle: my_table
With :-
Expected:
TableInfo{name='my_table', columns={name=Column{name='name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, age=Column{name='age', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
Found:
TableInfo{name='my_table', columns={name=Column{name='name', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}, age=Column{name='age', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
If you look at the Expected: section (schema according to the Entity) the definition for the name column has notNull=true but name column in the Found: section has notNull=false.
The schema's must match so you need to change the CREATE TABLE to use "name TEXT NOT NULL, "
BUT FINISH READING
I used the following to test/detect the above (initially creating v5 without the age column) and then using v6.
database.execSQL(
"CREATE TABLE my_table_new (" +
"id INTEGER NOT NULL, " +
"name TEXT, " + //<<<<<<<<<< incorrect
"age INTEGER NOT NULL, " +
"PRIMARY KEY(id))"
)
Log.d("MIG5_6", "Migration has been invoked - Inserting rows from original")
database.execSQL("INSERT INTO my_table_new (id, name, age) SELECT id, name, 0 FROM my_table")
Log.d("MIG5_6", "Migration has been invoked - Dropping Original Table")
database.execSQL("DROP TABLE my_table")
Log.d("MIG5_6", "Migration has been invoked - Renaming new table to original table")
database.execSQL("ALTER TABLE my_table_new RENAME TO my_table")
The log included :-
2021-04-14 21:20:41.502 D/MIG5_6: Migration has been invoked - Creating table to be renamed
2021-04-14 21:20:41.503 D/MIG5_6: Migration has been invoked - Inserting rows from original
2021-04-14 21:20:41.503 D/MIG5_6: Migration has been invoked - Dropping Original Table
2021-04-14 21:20:41.504 D/MIG5_6: Migration has been invoked - Renaming new table to original table
I'm unsure how you have got to the state you have but I suspect that you now have version 6 (this is stored in the database header file) and it is seeing a schema change and expecting you to go to version 7.
You have a bit of an issue now as the table has very likely been changed but does not have NOT NULL for the name column.
What you could do is have a Migration from 5 to 7 (or it might be 6-7 if the db is at V6) and then change the version to 7. This migration would be the same as 5 to 6 except that the name column has not null as per :-
val MIGRATION_5_7 = object : Migration(5, 7) {
override fun migrate(database: SupportSQLiteDatabase) {
Log.d("MIG5_7", "Migration has been invoked - Creating table to be renamed")
database.execSQL(
"CREATE TABLE my_table_new (" +
"id INTEGER NOT NULL, " +
"name TEXT NOT NULL, " + //<<<<<<<<<< Correct
"age INTEGER NOT NULL, " +
"PRIMARY KEY(id))"
)
Log.d("MIG5_7", "Migration has been invoked - Inserting rows from original")
database.execSQL("INSERT INTO my_table_new (id, name, age) SELECT id, name, 0 FROM my_table")
Log.d("MIG5_7", "Migration has been invoked - Dropping Original Table")
database.execSQL("DROP TABLE my_table")
Log.d("MIG5_7", "Migration has been invoked - Renaming new table to original table")
database.execSQL("ALTER TABLE my_table_new RENAME TO my_table")
}
}
Of course if you can afford to lose the data, just uninstall the app and rerun then no migrations are needed and the generated schema is used.
Generated Schema
If you use the Android View in Android Studio you will see java(generated) (always compile first before looking in it to get the latest) inside you will be able to locate RoomDB_Impl. Inside that you will see a method createAllTables this has the SQL that is used to create the table and is the SQL that is expected.
Using the SQL from the generated code should ensure that the schema is correct.
Upvotes: 2