MatrixDev / Roomigrant

Automated Android Room ORM migrations generator with compile-time code generation
MIT License
374 stars 23 forks source link

java.lang.IllegalStateException: Migration didn't properly handle: #22

Closed itsandreramon closed 3 years ago

itsandreramon commented 3 years ago

Room Version: 2.2.5 Roomigrant Version: 0.34

I just removed a single property from my entity class and then got this error message:

Before

@Parcelize
@TypeConverters(Converters::class)
@Entity(tableName = "customers")
data class EntityCustomer(

    @PrimaryKey
    @ColumnInfo(name = "uuid")
    val uuid: String = UUID.randomUUID().toString(),

    @ColumnInfo(name = "priority")
    var priority: Int = 1,

    @ColumnInfo(name = "is_manually_scheduled")
    val isManuallyScheduled: Boolean = false,

    @ColumnInfo(name = "is_deleted")
    val isDeleted: Boolean = false,

    @ColumnInfo(name = "owner")
    val owner: String = "undefined"
) : Parcelable

After

@Parcelize
@TypeConverters(Converters::class)
@Entity(tableName = "customers")
data class EntityCustomer(

    @PrimaryKey
    @ColumnInfo(name = "uuid")
    val uuid: String = UUID.randomUUID().toString(),

    @ColumnInfo(name = "priority")
    var priority: Int = 1,

    @ColumnInfo(name = "is_manually_scheduled")
    val isManuallyScheduled: Boolean = false,

    @ColumnInfo(name = "is_deleted")
    val isDeleted: Boolean = false,
) : Parcelable
2021-04-06 13:04:14.353 6601-6601/app.planner.debug E/AndroidRuntime: FATAL EXCEPTION: main
    Process: app.planner.debug, PID: 6601
    java.lang.IllegalStateException: Migration didn't properly handle: customers(app.planner.core.domain.entity.EntityCustomer).
     Expected:
    TableInfo{name='customers', columns={is_deleted=Column{name='is_deleted', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, priority=Column{name='priority', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, uuid=Column{name='uuid', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=1, defaultValue='null'}, is_manually_scheduled=Column{name='is_manually_scheduled', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
     Found:
    TableInfo{name='customers', columns={owner=Column{name='owner', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, is_deleted=Column{name='is_deleted', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, is_manually_scheduled=Column{name='is_manually_scheduled', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, priority=Column{name='priority', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, uuid=Column{name='uuid', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
        at androidx.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.java:103)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onUpgrade(FrameworkSQLiteOpenHelper.java:177)
        at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:416)
        at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:316)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:145)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:106)
        at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:476)
        at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:281)
        at androidx.room.RoomDatabase.query(RoomDatabase.java:324)
        at androidx.room.util.DBUtil.query(DBUtil.java:83)
        at app.planner.core.data.database.dao.ContactRoomDao_Impl$4.call(ContactRoomDao_Impl.java:138)
        at app.planner.core.data.database.dao.ContactRoomDao_Impl$4.call(ContactRoomDao_Impl.java:135)
        at androidx.room.CoroutinesRoom$Companion$createFlow$1$1.invokeSuspend(CoroutinesRoom.kt:81)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)

Im planning to go production soon but I am not sure about using this library, especially if I'm making bigger changes to the schema. I don't want the app to crash on startup.

itsandreramon commented 3 years ago

Same problem with my other Entity:

2021-04-06 14:24:21.314 6899-6899/app.planner.debug E/AndroidRuntime: FATAL EXCEPTION: main
    Process: app.planner.debug, PID: 6899
    java.lang.IllegalStateException: Migration didn't properly handle: visits(app.planner.core.domain.entity.EntityVisit).
     Expected:
    TableInfo{name='visits', columns={date=Column{name='date', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, is_deleted=Column{name='is_deleted', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, notes=Column{name='notes', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, uuid=Column{name='uuid', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=1, defaultValue='null'}, customer_uuid=Column{name='customer_uuid', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
     Found:
    TableInfo{name='visits', columns={date=Column{name='date', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, owner=Column{name='owner', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, is_deleted=Column{name='is_deleted', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, notes=Column{name='notes', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, customer_uuid=Column{name='customer_uuid', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, uuid=Column{name='uuid', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
        at androidx.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.java:103)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onUpgrade(FrameworkSQLiteOpenHelper.java:177)
        at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:416)
        at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:316)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:145)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:106)
        at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:476)
        at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:281)
        at androidx.room.RoomDatabase.query(RoomDatabase.java:324)
        at androidx.room.util.DBUtil.query(DBUtil.java:83)
        at app.planner.core.data.database.dao.ContactRoomDao_Impl$4.call(ContactRoomDao_Impl.java:138)
        at app.planner.core.data.database.dao.ContactRoomDao_Impl$4.call(ContactRoomDao_Impl.java:135)
        at androidx.room.CoroutinesRoom$Companion$createFlow$1$1.invokeSuspend(CoroutinesRoom.kt:81)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)
MatrixDev commented 3 years ago

@itsandreramon hello. thanks for finding a bug. I'll take a look as soon as I can. PS: if you find fix yourself - please paste it here

itsandreramon commented 3 years ago

the last snippet basically has owner and is_deleted switched so thats why its crashing

MatrixDev commented 3 years ago

@itsandreramon can you add generated migrations here?

itsandreramon commented 3 years ago

"owner" exists: (see visits table)

{
  "formatVersion": 1,
  "database": {
    "version": 8,
    "identityHash": "fd7af2cfd787d779a8982622d0390681",
    "entities": [
      {
        "tableName": "customers",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `priority` INTEGER NOT NULL, `is_manually_scheduled` INTEGER NOT NULL, `is_deleted` INTEGER NOT NULL, PRIMARY KEY(`uuid`))",
        "fields": [
          {
            "fieldPath": "uuid",
            "columnName": "uuid",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "priority",
            "columnName": "priority",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "isManuallyScheduled",
            "columnName": "is_manually_scheduled",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "isDeleted",
            "columnName": "is_deleted",
            "affinity": "INTEGER",
            "notNull": true
          }
        ],
        "primaryKey": {
          "columnNames": [
            "uuid"
          ],
          "autoGenerate": false
        },
        "indices": [],
        "foreignKeys": []
      },
      {
        "tableName": "visits",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `customer_uuid` TEXT NOT NULL, `date` TEXT NOT NULL, `is_deleted` INTEGER NOT NULL, `notes` TEXT NOT NULL, `owner` TEXT NOT NULL, PRIMARY KEY(`uuid`))",
        "fields": [
          {
            "fieldPath": "uuid",
            "columnName": "uuid",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "customerId",
            "columnName": "customer_uuid",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "date",
            "columnName": "date",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "isDeleted",
            "columnName": "is_deleted",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "notes",
            "columnName": "notes",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "owner",
            "columnName": "owner",
            "affinity": "TEXT",
            "notNull": true
          }
        ],
        "primaryKey": {
          "columnNames": [
            "uuid"
          ],
          "autoGenerate": false
        },
        "indices": [],
        "foreignKeys": []
      },
      {
        "tableName": "phone_numbers",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `customer_uuid` TEXT NOT NULL, `phone_number` TEXT, `hash` TEXT NOT NULL, PRIMARY KEY(`uuid`))",
        "fields": [
          {
            "fieldPath": "uuid",
            "columnName": "uuid",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "customerUuid",
            "columnName": "customer_uuid",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "phoneNumber",
            "columnName": "phone_number",
            "affinity": "TEXT",
            "notNull": false
          },
          {
            "fieldPath": "hash",
            "columnName": "hash",
            "affinity": "TEXT",
            "notNull": true
          }
        ],
        "primaryKey": {
          "columnNames": [
            "uuid"
          ],
          "autoGenerate": false
        },
        "indices": [],
        "foreignKeys": []
      },
      {
        "tableName": "contacts",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `first_name` TEXT NOT NULL, `last_name` TEXT NOT NULL, `phone_numbers` TEXT NOT NULL, `email_addresses` TEXT NOT NULL, `address` TEXT NOT NULL, `customer_uuid` TEXT, PRIMARY KEY(`id`))",
        "fields": [
          {
            "fieldPath": "id",
            "columnName": "id",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "firstName",
            "columnName": "first_name",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "lastName",
            "columnName": "last_name",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "phoneNumbers",
            "columnName": "phone_numbers",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "emailAddresses",
            "columnName": "email_addresses",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "address",
            "columnName": "address",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "customerUuid",
            "columnName": "customer_uuid",
            "affinity": "TEXT",
            "notNull": false
          }
        ],
        "primaryKey": {
          "columnNames": [
            "id"
          ],
          "autoGenerate": false
        },
        "indices": [],
        "foreignKeys": []
      }
    ],
    "views": [],
    "setupQueries": [
      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fd7af2cfd787d779a8982622d0390681')"
    ]
  }
}

"owner" removed:

{
  "formatVersion": 1,
  "database": {
    "version": 9,
    "identityHash": "27d0f8d007de3fb36388dbacfaef1b61",
    "entities": [
      {
        "tableName": "customers",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `priority` INTEGER NOT NULL, `is_manually_scheduled` INTEGER NOT NULL, `is_deleted` INTEGER NOT NULL, PRIMARY KEY(`uuid`))",
        "fields": [
          {
            "fieldPath": "uuid",
            "columnName": "uuid",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "priority",
            "columnName": "priority",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "isManuallyScheduled",
            "columnName": "is_manually_scheduled",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "isDeleted",
            "columnName": "is_deleted",
            "affinity": "INTEGER",
            "notNull": true
          }
        ],
        "primaryKey": {
          "columnNames": [
            "uuid"
          ],
          "autoGenerate": false
        },
        "indices": [],
        "foreignKeys": []
      },
      {
        "tableName": "visits",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `customer_uuid` TEXT NOT NULL, `date` TEXT NOT NULL, `is_deleted` INTEGER NOT NULL, `notes` TEXT NOT NULL, PRIMARY KEY(`uuid`))",
        "fields": [
          {
            "fieldPath": "uuid",
            "columnName": "uuid",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "customerId",
            "columnName": "customer_uuid",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "date",
            "columnName": "date",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "isDeleted",
            "columnName": "is_deleted",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "notes",
            "columnName": "notes",
            "affinity": "TEXT",
            "notNull": true
          }
        ],
        "primaryKey": {
          "columnNames": [
            "uuid"
          ],
          "autoGenerate": false
        },
        "indices": [],
        "foreignKeys": []
      },
      {
        "tableName": "phone_numbers",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `customer_uuid` TEXT NOT NULL, `phone_number` TEXT, `hash` TEXT NOT NULL, PRIMARY KEY(`uuid`))",
        "fields": [
          {
            "fieldPath": "uuid",
            "columnName": "uuid",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "customerUuid",
            "columnName": "customer_uuid",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "phoneNumber",
            "columnName": "phone_number",
            "affinity": "TEXT",
            "notNull": false
          },
          {
            "fieldPath": "hash",
            "columnName": "hash",
            "affinity": "TEXT",
            "notNull": true
          }
        ],
        "primaryKey": {
          "columnNames": [
            "uuid"
          ],
          "autoGenerate": false
        },
        "indices": [],
        "foreignKeys": []
      },
      {
        "tableName": "contacts",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `first_name` TEXT NOT NULL, `last_name` TEXT NOT NULL, `phone_numbers` TEXT NOT NULL, `email_addresses` TEXT NOT NULL, `address` TEXT NOT NULL, `customer_uuid` TEXT, PRIMARY KEY(`id`))",
        "fields": [
          {
            "fieldPath": "id",
            "columnName": "id",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "firstName",
            "columnName": "first_name",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "lastName",
            "columnName": "last_name",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "phoneNumbers",
            "columnName": "phone_numbers",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "emailAddresses",
            "columnName": "email_addresses",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "address",
            "columnName": "address",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "customerUuid",
            "columnName": "customer_uuid",
            "affinity": "TEXT",
            "notNull": false
          }
        ],
        "primaryKey": {
          "columnNames": [
            "id"
          ],
          "autoGenerate": false
        },
        "indices": [],
        "foreignKeys": []
      }
    ],
    "views": [],
    "setupQueries": [
      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '27d0f8d007de3fb36388dbacfaef1b61')"
    ]
  }
}
MatrixDev commented 3 years ago

@itsandreramon those are not migrations, but schemes :) I was talking about migration classes generated by the library.

itsandreramon commented 3 years ago

where do I find them?

MatrixDev commented 3 years ago

@itsandreramon you should pass generated migrations when creating Database.

https://developer.android.com/training/data-storage/room/migrating-db-versions

Room.databaseBuilder(applicationContext, MyDb::class.java, "database-name")
        .addMigrations(*MyDb_Migrations.build()).build()
itsandreramon commented 3 years ago

Oh yea, my bad. This is the relevant part I think:

private fun buildSchemeInfo_8(): SchemeInfo {
    val tables = HashMap<String, TableInfo>()
    val schemeInfo = SchemeInfo(8, tables)

    var tableInfo: TableInfo
    var indices: HashMap<String, IndexInfo>

    // Table "customers"
    indices = HashMap<String, IndexInfo>()
    tableInfo = TableInfo(schemeInfo, "customers",
        "CREATE TABLE IF NOT EXISTS `customers` (`uuid` TEXT NOT NULL, `priority` INTEGER NOT NULL, `is_manually_scheduled` INTEGER NOT NULL, `is_deleted` INTEGER NOT NULL, PRIMARY KEY(`uuid`))",
        indices)
    tables.put("customers", tableInfo)

    // Table "visits"
    indices = HashMap<String, IndexInfo>()
    tableInfo = TableInfo(schemeInfo, "visits",
        "CREATE TABLE IF NOT EXISTS `visits` (`uuid` TEXT NOT NULL, `customer_uuid` TEXT NOT NULL, `date` TEXT NOT NULL, `is_deleted` INTEGER NOT NULL, `notes` TEXT NOT NULL, `owner` TEXT NOT NULL, PRIMARY KEY(`uuid`))",
        indices)
    tables.put("visits", tableInfo)

    // Table "phone_numbers"
    indices = HashMap<String, IndexInfo>()
    tableInfo = TableInfo(schemeInfo, "phone_numbers",
        "CREATE TABLE IF NOT EXISTS `phone_numbers` (`uuid` TEXT NOT NULL, `customer_uuid` TEXT NOT NULL, `phone_number` TEXT, `hash` TEXT NOT NULL, PRIMARY KEY(`uuid`))",
        indices)
    tables.put("phone_numbers", tableInfo)

    // Table "contacts"
    indices = HashMap<String, IndexInfo>()
    tableInfo = TableInfo(schemeInfo, "contacts",
        "CREATE TABLE IF NOT EXISTS `contacts` (`id` INTEGER NOT NULL, `first_name` TEXT NOT NULL, `last_name` TEXT NOT NULL, `phone_numbers` TEXT NOT NULL, `email_addresses` TEXT NOT NULL, `address` TEXT NOT NULL, `customer_uuid` TEXT, PRIMARY KEY(`id`))",
        indices)
    tables.put("contacts", tableInfo)

    return schemeInfo
  }

  private fun buildSchemeInfo_9(): SchemeInfo {
    val tables = HashMap<String, TableInfo>()
    val schemeInfo = SchemeInfo(9, tables)

    var tableInfo: TableInfo
    var indices: HashMap<String, IndexInfo>

    // Table "customers"
    indices = HashMap<String, IndexInfo>()
    tableInfo = TableInfo(schemeInfo, "customers",
        "CREATE TABLE IF NOT EXISTS `customers` (`uuid` TEXT NOT NULL, `priority` INTEGER NOT NULL, `is_manually_scheduled` INTEGER NOT NULL, `is_deleted` INTEGER NOT NULL, PRIMARY KEY(`uuid`))",
        indices)
    tables.put("customers", tableInfo)

    // Table "visits"
    indices = HashMap<String, IndexInfo>()
    tableInfo = TableInfo(schemeInfo, "visits",
        "CREATE TABLE IF NOT EXISTS `visits` (`uuid` TEXT NOT NULL, `customer_uuid` TEXT NOT NULL, `date` TEXT NOT NULL, `is_deleted` INTEGER NOT NULL, `notes` TEXT NOT NULL, PRIMARY KEY(`uuid`))",
        indices)
    tables.put("visits", tableInfo)

    // Table "phone_numbers"
    indices = HashMap<String, IndexInfo>()
    tableInfo = TableInfo(schemeInfo, "phone_numbers",
        "CREATE TABLE IF NOT EXISTS `phone_numbers` (`uuid` TEXT NOT NULL, `customer_uuid` TEXT NOT NULL, `phone_number` TEXT, `hash` TEXT NOT NULL, PRIMARY KEY(`uuid`))",
        indices)
    tables.put("phone_numbers", tableInfo)

    // Table "contacts"
    indices = HashMap<String, IndexInfo>()
    tableInfo = TableInfo(schemeInfo, "contacts",
        "CREATE TABLE IF NOT EXISTS `contacts` (`id` INTEGER NOT NULL, `first_name` TEXT NOT NULL, `last_name` TEXT NOT NULL, `phone_numbers` TEXT NOT NULL, `email_addresses` TEXT NOT NULL, `address` TEXT NOT NULL, `customer_uuid` TEXT, PRIMARY KEY(`id`))",
        indices)
    tables.put("contacts", tableInfo)

    return schemeInfo
  }
MatrixDev commented 3 years ago

@itsandreramon those are schemes as well :) in your case it should be Database_Migration_8_9 or something. this is where all migration logic is happening.

itsandreramon commented 3 years ago

Is this what you're looking for?

public object TemporyRoomDatabase_Migration_8_9 : Migration(8, 9) {
  public override fun migrate(database: SupportSQLiteDatabase): Unit {
    database.execSQL("""CREATE TABLE IF NOT EXISTS `visits_MERGE_TABLE` (`uuid` TEXT NOT NULL, `customer_uuid` TEXT NOT NULL, `date` TEXT NOT NULL, `is_deleted` INTEGER NOT NULL, `notes` TEXT NOT NULL, PRIMARY KEY(`uuid`))""")
    database.execSQL("""INSERT INTO `visits_MERGE_TABLE` (`uuid`,`customer_uuid`,`date`,`is_deleted`,`notes`) SELECT `visits`.`uuid`,`visits`.`customer_uuid`,`visits`.`date`,`visits`.`is_deleted`,`visits`.`notes` FROM `visits`""")
    database.execSQL("""DROP TABLE IF EXISTS `visits`""")
    database.execSQL("""ALTER TABLE `visits_MERGE_TABLE` RENAME TO `visits`""")
  }
}
MatrixDev commented 3 years ago

Well, I don't see any issues in the generated code for migrations. Are you sure you're actually using migrations when creating a database?

PS: You can also debug generated code and see if they are actually called and where is the actual problem.

itsandreramon commented 3 years ago

This is how I instantiate the database:

private fun buildDatabase(context: Context, name: String): TemporyRoomDatabase {
    return Room.databaseBuilder(context, TemporyRoomDatabase::class.java, name)
        .addMigrations(*TemporyRoomDatabase_Migrations.build())
        .fallbackToDestructiveMigration()
        .build()
}

My Gradle config:


android {
    compileSdkVersion buildConfig.compileSdk

    defaultConfig {
        minSdkVersion buildConfig.minSdk
        targetSdkVersion buildConfig.targetSdk

        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [
                    "room.schemaLocation": "$projectDir/schemas".toString(),
                    "room.incremental"   : "true"
                ]
            }
        }
    }
    ...
}
MatrixDev commented 3 years ago

@itsandreramon please debug whats happening during migration and why it is failing. I can't reproduce it on my side.

itsandreramon commented 3 years ago

It seems to work now. I have no idea why. I swear that it didn't work at first. Will keep you updated if any further issues arise!

MatrixDev commented 3 years ago

if you'll fail to reproduce it again - please close this issue

MatrixDev commented 3 years ago

Seams issue was no related to the library

itsandreramon commented 3 years ago

I am getting this error again. I only added one field to the schema, it told me to upgrade the version, I did it and this is what I got:

2021-04-20 16:30:26.732 23449-23449/app.tempory.debug E/AndroidRuntime: FATAL EXCEPTION: main
    Process: app.tempory.debug, PID: 23449
    java.lang.IllegalStateException: Migration didn't properly handle: contacts(app.tempory.core.domain.entity.EntityContact).
     Expected:
    TableInfo{name='contacts', columns={phone_numbers=Column{name='phone_numbers', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, email_addresses=Column{name='email_addresses', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, address=Column{name='address', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, customer_uuid=Column{name='customer_uuid', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, lookup_key=Column{name='lookup_key', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, last_name=Column{name='last_name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}, first_name=Column{name='first_name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
     Found:
    TableInfo{name='contacts', columns={phone_numbers=Column{name='phone_numbers', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, email_addresses=Column{name='email_addresses', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, address=Column{name='address', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, customer_uuid=Column{name='customer_uuid', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, last_name=Column{name='last_name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}, first_name=Column{name='first_name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
        at androidx.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.java:103)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onUpgrade(FrameworkSQLiteOpenHelper.java:177)
        at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:416)
        at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:316)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:145)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:106)
        at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:476)
        at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:281)
        at androidx.room.RoomDatabase.query(RoomDatabase.java:324)
        at androidx.room.util.DBUtil.query(DBUtil.java:83)
        at app.tempory.core.data.database.dao.ContactRoomDao_Impl$4.call(ContactRoomDao_Impl.java:145)
        at app.tempory.core.data.database.dao.ContactRoomDao_Impl$4.call(ContactRoomDao_Impl.java:142)
        at androidx.room.CoroutinesRoom$Companion$createFlow$1$1.invokeSuspend(CoroutinesRoom.kt:81)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)

The migration seems to be empty, I have no Idea why:

public object TemporyRoomDatabase_Migration_16_17 : Migration(16, 17) {
  public override fun migrate(database: SupportSQLiteDatabase): Unit {
  }
}
itsandreramon commented 3 years ago

There is definitely some error in the library. I just don't know where or why its happening.

itsandreramon commented 3 years ago

I now tried to return to the old schema, lower the DB version, recompile and then change it back, increase the version number and now my Migration looks like this: (which is correct)

public object TemporyRoomDatabase_Migration_16_17 : Migration(16, 17) {
    public override fun migrate(database: SupportSQLiteDatabase): Unit {
        database.execSQL("""ALTER TABLE `contacts` ADD `lookup_key` TEXT NOT NULL DEFAULT ''""")
    }
}
MatrixDev commented 3 years ago

@itsandreramon please check which columns are not migrated properly and localise the error.

itsandreramon commented 3 years ago

As you can see it from the code, lookup_key has not been added to the table on the first run.

MatrixDev commented 3 years ago

It cannot be skipped. what probably happened is that you've changed tables, compiled you code and only than changed database version. Library doesn't cache anything so it can't produce different results from one time to other.

itsandreramon commented 3 years ago

Ah I see, do you mind adding some hint to the docs?

MatrixDev commented 3 years ago

ok, I'll add