Caused by: java.lang.IllegalStateException: Migration didn't properly handle: Expected TableInfo{name='project', columns={venueName=Column{name='venueName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, nfcMerchantId=Column{name='nfcMerchantId', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, nfcPrivateKey=Column{name='nfcPrivateKey', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, endDate=Column{name='endDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, scheduledAnonymisationDate=Column{name='scheduledAnonymisationDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, nfcProfileId=Column{name='nfcProfileId', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, name=Column{name='name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, timeZone=Column{name='timeZone', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=1, defaultValue='null'}, uuid=Column{name='uuid', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, nfcCollectorId=Column{name='nfcCollectorId', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, startDate=Column{name='startDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[Index{name='index_project_uuid', unique=false, columns=[uuid], orders=[ASC]}]}
Found TableInfo{name='project', columns={venueName=Column{name='venueName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, endDate=Column{name='endDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, scheduledAnonymisationDate=Column{name='scheduledAnonymisationDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, nfcProfileId=Column{name='nfcProfileId', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, name=Column{name='name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, timeZone=Column{name='timeZone', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=1, defaultValue='null'}, uuid=Column{name='uuid', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, startDate=Column{name='startDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[Index{name='index_project_uuid', unique=false, columns=[uuid], orders=[ASC]}]}
I have upgraded my app on top of the previous version and I got this error when I open the new upgraded version of my app. I have added migration class and added the newly added fields and also I have increased the database version. This is my entity class.
@Entity(
tableName = "project",
indices = [Index("uuid")]
)
data class ProjectEntity(
@PrimaryKey
val id: String,
val uuid: String,
val name: String,
val startDate: String?,
val endDate: String?,
val venueName: String?,
val timeZone: String?,
val scheduledAnonymisationDate: String?,
val nfcCollectorId: String?,
val nfcMerchantId: String?,
val nfcPrivateKey: String?,
val nfcProfileId: String?
)
This is my database class.
@Database(entities = {ProjectEntity.class}, version = 23)
public abstract class RoomDatabaseImpl extends RoomDatabase {
abstract fun projectDao(): ProjectDao
}
This is my migration class.
internal object Migration18To19 : Migration(18, 19) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `project` ADD COLUMN `nfcCollectorId` TEXT")
database.execSQL("ALTER TABLE `project` ADD COLUMN `nfcMerchantId` TEXT")
database.execSQL("ALTER TABLE `project` ADD COLUMN `nfcPrivateKey` TEXT")
database.execSQL("ALTER TABLE `project` ADD COLUMN `nfcProfileId` TEXT")
}
}
and my current database version is 23.
I have also made few other changes in the other tables and added migration class for all that and incremented the database version accordingly. My current latest migration class is Migration22To23 and version is 23. I am not really sure why I am getting this error. I have also tried debugging logs and got the below details.
45 {
type=table
name=project
tbl_name=project
rootpage=49
sql=CREATE TABLE `project` (`id` TEXT NOT NULL, `uuid` TEXT NOT NULL, `name` TEXT NOT NULL, `startDate` TEXT, `endDate` TEXT, `venueName` TEXT, `timeZone` TEXT, `scheduledAnonymisationDate` TEXT, `nfcCollectorId` TEXT, `nfcMerchantId` TEXT, `nfcPrivateKey` TEXT, `nfcProfileId` TEXT, PRIMARY KEY(`id`))
}
46 {
type=index
name=sqlite_autoindex_project_1
tbl_name=project
rootpage=50
sql=null
}
47 {
type=index
name=index_project_uuid
tbl_name=project
rootpage=52
sql=CREATE INDEX `index_project_uuid` ON `project` (`uuid`)
}
It seems all the columns are added in 46 migration. But I am not sure why it is not working. I have also found one more information. We initially made nfcCollectorId,nfcMerchantId,nfcPrivateKey,nfcProfileId as not nullable and in between we have changed that as nullable in ProjectEntity. I am not sure that is causing the issue.
Considering that you have picked one Migration (18-19) from many and that that Migration should not be relevant as the version is supposedly moving from 22-23.
There could be numerous causes.
Certainly from the evidence, that:-
nfcCollectorId
nfcMerchantId
nfcPrivateKey
are Expected but not Found, it would be the 18-19 Migration that is required. However, 18-19 would fail as the nfcProfileId
column has been found unless there is a Migration that Drops the column.
Could someone help me to solve this issue?
Perhaps the following could be of use
You need to get some understanding of what is going on, from what version to what version and whether or not all migrations are being processed and what is changing when those migrations are invoked.
I would suggest considering the following code that enables such debugging information to be extracted (to replace/amend RoomDatabaseImpl
):-
const val DB_VERSION = 3 /* being a constant allows the value to be used e.g. in preOpenSchemaDump */
const val DB_NAME = "room_database_impl.db" /* similar */
const val TAG="DBINFO" /* tag so all debug messages (bar schema dumps) can be easily located */
@Database(entities = [ProjectEntity::class], version = DB_VERSION)
abstract class RoomDatabaseImpl: RoomDatabase() {
abstract fun projectDao(): ProjectDao
companion object {
private var instance: RoomDatabaseImpl?=null
fun getInstance(context: Context): RoomDatabaseImpl {
if (instance==null) {
preOpenSchemaDump(context)
instance= Room.databaseBuilder(context,RoomDatabaseImpl::class.java, DB_NAME)
.allowMainThreadQueries() /* For brevity */
.addCallback(CB) /* added for debugging */
.addMigrations(Migration1To2,Migration2To3)
.build()
}
return instance as RoomDatabaseImpl
}
fun preOpenSchemaDump(context: Context) {
if (context.getDatabasePath(DB_NAME).exists()) {
val db = SQLiteDatabase.openDatabase(
context.getDatabasePath(DB_NAME).path,
null,
SQLiteDatabase.OPEN_READWRITE
)
Log.d(TAG,"Database Version is ${db.version}: Room version will be ${DB_VERSION}" )
dumpSchema("PreOpen invoked", db)
db.close()
} else {
Log.d(TAG,"PreOpen Invoked but Database does not exist yet (nothing to dump so skipped)")
}
}
internal object Migration2To3 : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
dumpSchema("Before Migration 2 to 3 invoked", database)
database.execSQL("ALTER TABLE `project` ADD COLUMN `nfcCollectorId` TEXT")
database.execSQL("ALTER TABLE `project` ADD COLUMN `nfcMerchantId` TEXT")
database.execSQL("ALTER TABLE `project` ADD COLUMN `nfcPrivateKey` TEXT")
//database.execSQL("ALTER TABLE `project` ADD COLUMN `nfcProfileId` TEXT") /* commented out as would fail */
dumpSchema("Post Migration 2 to 3 invoked", database)
}
}
internal object Migration1To2 : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
dumpSchema("Before Migration 1 to 2 invoked", database)
database.execSQL("ALTER TABLE `project` ADD COLUMN `nfcProfileId` TEXT")
dumpSchema("Post Migration 1 to 2 invoked", database)
}
}
/* CallBack for logging purposes */
internal object CB: Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
dumpSchema("OnCreate Callback initiated",db)
}
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
dumpSchema("OnOpen Callback initiated",db)
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
super.onDestructiveMigration(db)
dumpSchema("OnDestructiveMigration Callback initiated",db)
}
}
private fun dumpSchema(reason: String, db: SupportSQLiteDatabase, suppressDump: Boolean=false) {
Log.d(TAG,"$reason - dumping schema to log:-")
if (!suppressDump) {
val csr = db.query("SELECT * FROM sqlite_master")
DatabaseUtils.dumpCursor(csr)
csr.close()
}
}
private fun dumpSchema(reason: String, db: SQLiteDatabase, suppressDump: Boolean=false) {
Log.d(TAG,"$reason - dumping schema to log:-")
if (!suppressDump) {
val csr = db.rawQuery("SELECT * FROM sqlite_master", null)
DatabaseUtils.dumpCursor(csr)
csr.close()
}
}
}
As an example.
Initially DB_VERSION is 1 and all the nfc.... columns have been commented out and a fresh install is performed. The log includes:-
2024-04-24 14:12:16.466 D/DBINFO: PreOpen Invoked but Database does not exist yet (nothing to dump so skipped)
2024-04-24 14:12:16.533 D/DBINFO: OnCreate Callback initiated - dumping schema to log:-
2024-04-24 14:12:16.533 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@68ac7e0
2024-04-24 14:12:16.534 I/System.out: 0 {
2024-04-24 14:12:16.534 I/System.out: type=table
2024-04-24 14:12:16.534 I/System.out: name=android_metadata
2024-04-24 14:12:16.534 I/System.out: tbl_name=android_metadata
2024-04-24 14:12:16.534 I/System.out: rootpage=3
2024-04-24 14:12:16.534 I/System.out: sql=CREATE TABLE android_metadata (locale TEXT)
2024-04-24 14:12:16.534 I/System.out: }
2024-04-24 14:12:16.534 I/System.out: 1 {
2024-04-24 14:12:16.534 I/System.out: type=table
2024-04-24 14:12:16.534 I/System.out: name=project
2024-04-24 14:12:16.534 I/System.out: tbl_name=project
2024-04-24 14:12:16.534 I/System.out: rootpage=4
2024-04-24 14:12:16.534 I/System.out: sql=CREATE TABLE `project` (`id` TEXT NOT NULL, `uuid` TEXT NOT NULL, `name` TEXT NOT NULL, `startDate` TEXT, `endDate` TEXT, `venueName` TEXT, `timeZone` TEXT, `scheduledAnonymisationDate` TEXT, PRIMARY KEY(`id`))
2024-04-24 14:12:16.535 I/System.out: }
....
2024-04-24 14:12:16.544 D/DBINFO: OnOpen Callback initiated - dumping schema to log:-
2024-04-24 14:12:16.547 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@a4bf93f
....
Then the nfc columns are uncommented and DB_VERSION set to 3 and the App rerun, the log then includes:-
2024-04-24 14:18:13.256 D/DBINFO: Database Version is 1: Room version will be 3
2024-04-24 14:18:13.256 D/DBINFO: PreOpen invoked - dumping schema to log:-
2024-04-24 14:18:13.257 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@aa6612
....
2024-04-24 14:18:13.258 I/System.out: 1 {
2024-04-24 14:18:13.258 I/System.out: type=table
2024-04-24 14:18:13.258 I/System.out: name=project
2024-04-24 14:18:13.258 I/System.out: tbl_name=project
2024-04-24 14:18:13.258 I/System.out: rootpage=4
2024-04-24 14:18:13.258 I/System.out: sql=CREATE TABLE `project` (`id` TEXT NOT NULL, `uuid` TEXT NOT NULL, `name` TEXT NOT NULL, `startDate` TEXT, `endDate` TEXT, `venueName` TEXT, `timeZone` TEXT, `scheduledAnonymisationDate` TEXT, PRIMARY KEY(`id`))
....
2024-04-24 14:18:13.322 D/DBINFO: Before Migration 1 to 2 invoked - dumping schema to log:-
2024-04-24 14:18:13.323 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@a4bf93f
....
2024-04-24 14:18:13.323 I/System.out: 1 {
2024-04-24 14:18:13.323 I/System.out: type=table
2024-04-24 14:18:13.323 I/System.out: name=project
2024-04-24 14:18:13.323 I/System.out: tbl_name=project
2024-04-24 14:18:13.323 I/System.out: rootpage=4
2024-04-24 14:18:13.323 I/System.out: sql=CREATE TABLE `project` (`id` TEXT NOT NULL, `uuid` TEXT NOT NULL, `name` TEXT NOT NULL, `startDate` TEXT, `endDate` TEXT, `venueName` TEXT, `timeZone` TEXT, `scheduledAnonymisationDate` TEXT, PRIMARY KEY(`id`))
....
2024-04-24 14:18:13.334 D/DBINFO: Post Migration 2 to 3 invoked - dumping schema to log:-
2024-04-24 14:18:13.330 D/DBINFO: Before Migration 2 to 3 invoked - dumping schema to log:-
2024-04-24 14:18:13.330 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@8d33755
....
2024-04-24 14:18:13.331 I/System.out: 1 {
2024-04-24 14:18:13.331 I/System.out: type=table
2024-04-24 14:18:13.331 I/System.out: name=project
2024-04-24 14:18:13.331 I/System.out: tbl_name=project
2024-04-24 14:18:13.331 I/System.out: rootpage=4
2024-04-24 14:18:13.331 I/System.out: sql=CREATE TABLE `project` (`id` TEXT NOT NULL, `uuid` TEXT NOT NULL, `name` TEXT NOT NULL, `startDate` TEXT, `endDate` TEXT, `venueName` TEXT, `timeZone` TEXT, `scheduledAnonymisationDate` TEXT, `nfcProfileId` TEXT, PRIMARY KEY(`id`))
....
2024-04-24 14:18:13.334 D/DBINFO: Post Migration 2 to 3 invoked - dumping schema to log:-
2024-04-24 14:18:13.334 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@d88a56a
....
2024-04-24 14:18:13.335 I/System.out: 1 {
2024-04-24 14:18:13.335 I/System.out: type=table
2024-04-24 14:18:13.335 I/System.out: name=project
2024-04-24 14:18:13.336 I/System.out: tbl_name=project
2024-04-24 14:18:13.336 I/System.out: rootpage=4
2024-04-24 14:18:13.336 I/System.out: sql=CREATE TABLE `project` (`id` TEXT NOT NULL, `uuid` TEXT NOT NULL, `name` TEXT NOT NULL, `startDate` TEXT, `endDate` TEXT, `venueName` TEXT, `timeZone` TEXT, `scheduledAnonymisationDate` TEXT, `nfcProfileId` TEXT, `nfcCollectorId` TEXT, `nfcMerchantId` TEXT, `nfcPrivateKey` TEXT, PRIMARY KEY(`id`))
Now if a deliberate issue is introduced (in 1-2) e.g.
database.execSQL("ALTER TABLE project
ADD COLUMN nfcOOOPS
TEXT")
And then version 1 is installed followed by version 3 as done previously then
But 3 gets the Expected not Found but not until after both migrations as per the following extracts from the log:-
2024-04-24 14:34:12.289 D/DBINFO: Database Version is 1: Room version will be 3
2024-04-24 14:34:12.289 D/DBINFO: PreOpen invoked - dumping schema to log:-
2024-04-24 14:34:12.289 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@aa6612
....
2024-04-24 14:34:12.290 I/System.out: sql=CREATE TABLE `project` (`id` TEXT NOT NULL, `uuid` TEXT NOT NULL, `name` TEXT NOT NULL, `startDate` TEXT, `endDate` TEXT, `venueName` TEXT, `timeZone` TEXT, `scheduledAnonymisationDate` TEXT, PRIMARY KEY(`id`))
....
2024-04-24 14:34:12.348 D/DBINFO: Before Migration 1 to 2 invoked - dumping schema to log:-
2024-04-24 14:34:12.349 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@a4bf93f
....
2024-04-24 14:34:12.353 D/DBINFO: Post Migration 1 to 2 invoked - dumping schema to log:-
2024-04-24 14:34:12.354 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@e04820c
....
2024-04-24 14:34:12.355 I/System.out: sql=CREATE TABLE `project` (`id` TEXT NOT NULL, `uuid` TEXT NOT NULL, `name` TEXT NOT NULL, `startDate` TEXT, `endDate` TEXT, `venueName` TEXT, `timeZone` TEXT, `scheduledAnonymisationDate` TEXT, `nfcOOOPS` TEXT, PRIMARY KEY(`id`))
....
2024-04-24 14:34:12.356 D/DBINFO: Before Migration 2 to 3 invoked - dumping schema to log:-
2024-04-24 14:34:12.357 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@8d33755
....
2024-04-24 14:34:12.360 D/DBINFO: Post Migration 2 to 3 invoked - dumping schema to log:-
2024-04-24 14:34:12.360 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@d88a56a
....
2024-04-24 14:34:12.361 I/System.out: sql=CREATE TABLE `project` (`id` TEXT NOT NULL, `uuid` TEXT NOT NULL, `name` TEXT NOT NULL, `startDate` TEXT, `endDate` TEXT, `venueName` TEXT, `timeZone` TEXT, `scheduledAnonymisationDate` TEXT, `nfcOOOPS` TEXT, `nfcCollectorId` TEXT, `nfcMerchantId` TEXT, `nfcPrivateKey` TEXT, PRIMARY KEY(`id`))
....
2024-04-24 14:34:12.947 E/AndroidRuntime: FATAL EXCEPTION: main
Process: a.a.so78373886multiplemigrations, PID: 16141
java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so78373886multiplemigrations/a.a.so78373886multiplemigrations.MainActivity}: java.lang.IllegalStateException: Migration didn't properly handle: project(a.a.so78373886multiplemigrations.ProjectEntity).
Expected:
TableInfo{name='project', columns={venueName=Column{name='venueName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, nfcMerchantId=Column{name='nfcMerchantId', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, nfcPrivateKey=Column{name='nfcPrivateKey', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, endDate=Column{name='endDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, scheduledAnonymisationDate=Column{name='scheduledAnonymisationDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, nfcProfileId=Column{name='nfcProfileId', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, name=Column{name='name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='undefined'}, timeZone=Column{name='timeZone', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, id=Column{name='id', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=1, defaultValue='undefined'}, uuid=Column{name='uuid', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='undefined'}, nfcCollectorId=Column{name='nfcCollectorId', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, startDate=Column{name='startDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}}, foreignKeys=[], indices=[Index{name='index_project_uuid', unique=false, columns=[uuid], orders=[ASC]'}]}
Found:
TableInfo{name='project', columns={id=Column{name='id', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=1, defaultValue='undefined'}, uuid=Column{name='uuid', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='undefined'}, name=Column{name='name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='undefined'}, startDate=Column{name='startDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, endDate=Column{name='endDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, venueName=Column{name='venueName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, timeZone=Column{name='timeZone', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, scheduledAnonymisationDate=Column{name='scheduledAnonymisationDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, nfcOOOPS=Column{name='nfcOOOPS', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, nfcCollectorId=Column{name='nfcCollectorId', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, nfcMerchantId=Column{name='nfcMerchantId', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, nfcPrivateKey=Column{name='nfcPrivateKey', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}}, foreignKeys=[], indices=[Index{name='index_project_uuid', unique=false, columns=[uuid], orders=[ASC]'}]}