Four migrations, three production incidents. Here is the checklist I use now.
What went wrong, in order
- Added a non-null column with no default. Every existing row failed.
- Renamed a column by dropping and recreating. Deleted everyone's data.
- Bumped the version and forgot the migration entirely, so
fallbackToDestructiveMigrationquietly wiped the table.
That third one is the dangerous one, because it does not crash. It just loses everything and carries on.
The rule I follow now
val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE puzzle ADD COLUMN streak INTEGER NOT NULL DEFAULT 0")
}
}
Every added column gets a default. Every rename is a create-copy-drop, written
out by hand. fallbackToDestructiveMigration never ships in a release build.
Test it against a real old database
Room's MigrationTestHelper will run a migration against a schema you exported
earlier. Export every schema, commit them, and write the test. It takes ten
minutes and it is the only thing that catches the case where you migrate a
database you no longer have on your machine.