How do I correct a wrong Room migration? - android

I added a column to a table, then added the following migration (version 56 to 57):
private val MIGRATION_56_57 = object : Migration(56, 57) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `app_stage` ADD COLUMN hasSeenBusinessOwnerQuestion INTEGER DEFAULT 0 NOT NULL")
}
}
After building and releasing the app to our existing users, they get a migration error and the app crashes. To correct the error, I just need to change:hasSeenBusinessOwnerQuestion INTEGER DEFAULT 0 NOT NULL
to:hasSeenBusinessOwnerQuestion INTEGER DEFAULT 0 .
Should I just add another migration from version 57 to 58 as:
private val MIGRATION_57_58 = object : Migration(57, 58) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `app_stage` ADD COLUMN hasSeenBusinessOwnerQuestion INTEGER DEFAULT 0 ")
}
}
?
Will existing users upgrading from version 56 all the way to 58 get that same migration error? How do I add another migration to version 58 to avoid migration errors?

? Will existing users upgrading from version 56 all the way to 58 get that same migration error?
No Room will invoke all the migrations in sequence (56-57 and then 57-58) and then after all the migrations have been performed continue with the database build.
However you cannot use the ALTER command to ADD an already existing column, which would be the case going from 57-58. So the migration would fail.
Although later versions of SQLite (3.35.0) support ALTER TABLE .... DROP COLUMN ...., this version is not available at present on Android devices and that there is no ALTER COLUMN. You will have to use an alternative means to alter the column.
The DROP COLUMN is also quite restrictive
You could do the following (where ? represents the table in question):-
DROP TABLE IF EXISTS ?_old
this is just in-case it exists (it should not)
Use the ALTER TABLE ? RENAME TO ?_old (_old just a suggested name for what is to be a temporary version of the table)
Use CREATE TABLE IF NOT EXISTS ....
Room demands that the create table SQL creates the table according to how it interprets the class annotated with #Entity.
It is suggested that you retrieve the create table SQL from the generated java that is available after compiling the project. The SQL will be in the createAllTables method of the class that is the same as the class that is annotated with #Database but suffixed with _Impl
Use INSERT INTO ? SELECT * FROM ?_old to copy existing data into the newly created version of the table
Use DROP TABLE IF EXISTS ?
So apart from the CREATE TABLE .... (which would have to be altered, see points above) the following would cater for all scenarios (new users, users on 57 and users on 56) :-
private val MIGRATION_57_58 = object : Migration(57, 58) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS `app_stage_old`")
database.execSQL("ALTER TABLE `app_stage` RENAME TO `app_stage_old`")
/* NOTE the CREATE TABLE IF NOT EXISTS .... SHOULD BE ALTERED ACCORDINGLY */
database.execSQL("CREATE TABLE IF NOT EXISTS `app_stage` (`id` INTEGER, `name` TEXT NOT NULL,`hasSeenBusinessOwnerQuestion` INTEGER DEFAULT 0, PRIMARY KEY(`id`))")
database.execSQL("INSERT INTO `app_stage` SELECT * FROM `app_stage_old`")
database.execSQL("DROP TABLE IF EXISTS `app_stage_old`")
}
}

Related

Room database migration with missing 1.json schema file

I am about the migrate my room database from 1 to 2.
In version 1 exportSchema was set to false. I was unaware of the impact at the time.
Therefore no 1.json schema file is available on device running the app so far.
In version 1 there is a class, let's call it Mango as follows:
#Entity(tableName="mango)
data class Mango(
#PrimaryKey(autoGenerate = true) val id: Int =0,
val carbs: Float = 0f
){...}
In version 2 the field carbs should change to carbohydrate. This is how I do it in my RoomDatabase class.
#Database(
entities = [Mango::class], version = 2, exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
...
fun getDatabase(
context: Context
): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context,
AppDatabase::class.java,
"my_database"
).addMigrations(MIGRATION_1_2).build()
INSTANCE = instance
instance
}
}
...
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Mango RENAME COLUMN cabrs TO carbohydrate")
}
It works on emulator in android studio. And this is how I test it.
Uninstall the app
Run version 1
Switch code to version 2 and run version 2
Result: Working
I have created a release version and sent it via google play to test it in a real life stuation, and I get the following error when I update the app with the new version 2:
Fatal Exception: android.database.sqlite.SQLiteException: near "COLUMN": syntax error (code
1): , while compiling: ALTER TABLE Mango RENAME COLUMN carbs TO carbohydrate
#################################################################
Error Code : 1 (SQLITE_ERROR)
Caused By : SQL(query) error or missing database.
(near "COLUMN": syntax error (code 1): , while compiling: ALTER TABLE Mango RENAME COLUMN
carbs TO carbohydrate).
If it is because of the missing 1.json file, then how can I fix this?
Thanks
If it is because of the missing 1.json file, then how can I fix this?
The issue is probably not the schema but is probably that the "real life run" was on an android version that doesn't include release 3.25.0 or greater of SQLite.
Typically unsupported features result in a Syntax Error that can be confusing is it tends to mention where the error was found as it doesn't know about the new syntax.
i.e. only devices with API 30+ (when a jump was made from 3.22.0 to 3.28.0)
as per https://developer.android.com/reference/android/database/sqlite/package-summary
SQLite 3.25.0 release documentation:-
2018-09-15 (3.25.0)
Add support for window functions
Enhancements the ALTER TABLE command:
Add support for renaming columns within a table using ALTER TABLE table RENAME COLUMN oldname TO newname.
-Fix table rename feature so that it also updates references to the renamed table in triggers and views.
....
If you need to target devices less than API 30, then you may not be able to use AutoMigration but will instead have a manual migration that:-
renames the original table
create the new table (copy the SQL from the createAllTables method in the class that is the same name as the #Database annotated class but suffixed with _Impl that can be found in the java(generated) via the Android View (I believe CRTL B can also be used)).
then execute the SQL INSERT INTO <new_table> SELECT * FROM <the_renamed_original_table>
Note that this assumes that the columns are in exactly the same position. It would be safer to use INSERT INTO new_table (<ALL_THE_COLUMNS_NAMES_OF_THE_NEW_TABLE_COMMA_SEPARATED>) SELECT (<THE_RESPECTIVE_COLUMNS_OF_THE_RENAMED_TABLE_COMMA_SEPARATED>)
Note anything enclosed within < and > should be changed accordingly, the enclosed text explains the change(s)
Note the above is in-principle code, it has not been compiled/run or tested so may contain some minor errors.

Android SQLite onUpgrade add a new table without touching the old ones

I have a database setup in android right now. the database and tables work fine. However, when I want to add a new table and use onUpgrade - the database is created, but doesn't properly work. As in the table looks created but I can't add data to it. when I try the app crashes. All the version 1 tables still look fine.
here is what my onUpgrade looks like:
private val newDatabase = listOf<String>(
"Database1"
)
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
newDatabase.forEach{
val table2 = "CREATE TABLE $it ($ID INTEGER PRIMARY KEY, $AVERAGETIME TEXT DEFAULT \"0\",$DATE TEXT DEFAULT \"0\")"
db?.execSQL(table2)
}
}
my oncreate looks exactly the same, just uses a different array of strings:
override fun onCreate(db: SQLiteDatabase?) {
originalDatabase.forEach {
val table1 =
"CREATE TABLE $it ($ID INTEGER PRIMARY KEY, $AVERAGETIME TEXT DEFAULT \"0\",$DATE TEXT DEFAULT \"0\")"
db?.execSQL(table1)
}
}
Is this the best way to add a new table? once the table is made, it seems to be blank and not work right. even though I have default values set to it.
I tried looking through other posts but can't seem to find one concrete answer
Here is a working example Delivery Droid Database Class

Android Room: How to Migrate Column Renaming?

Issue
My app is crashing because I am not handling migration properly. I'm looking for a solution to migrate the name of 1 column in my table.
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.
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 {...}
Attempted Solution
After reading a Google Developer Advocate's explanation Understanding migrations with Room, I attempted her solution outlined in the post's section Migrations with complex schema changes which entails making a copy of the original table, deleting the old table, then renaming the newly created table.
With the following approach below there is a runtime error on this line: database.execSQL("INSERT INTO content_new (id, dismissCount) SELECT id, archiveCount FROM users"); because I already cleared my app's cache so the old table no longer exists.
Can I update a single column without re-creating the entire table?
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(SupportSQLiteDatabase database) {
// 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 users");
// 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");
}
};
Solution
Thanks to the guidance from #TimBiegeleisen we discovered that the Android implementation of SQLite 3.19 for API 27 and 28 has not yet upgraded to the version 3.25 SQLite which allows this feature outlined in this StackOverflow post.
Once Android upgrades a command such as this to alter a table column will be possible: database.execSQL("ALTER TABLE content RENAME COLUMN archiveCount TO dismissCount")
There is a solution without migration - use ColumnInfo:
data class Content(#PrimaryKey var id: String, #ColumnInfo(name = "archiveCount") var dismissCount: Double) : Parcelable{...}
Database column will be still archiveCount, but in Kotlin property will be renamed.

Deleting primary key in Room

I'm trying to set my primary key to one after deleting all items in table:
#Query("DELETE FROM myTable")
fun deleteTable()
#Query("DELETE FROM sqlite_sequence WHERE name = 'myTable'")
fun clearPrimaryKey()
but it doesn't work and after adding items again, their number don't start from 1. Any idea how to clear primary key in Room library?
Let us say your table name is myTable. Then just do this:
#Query("ALTER TABLE myTable AUTO_INCREMENT = 1")
fun clearPrimaryKey()
It resets the number of auto-generated primary key.
UPDATE:
Another way to do both deleting and resetting is this:
#Query("TRUNCATE TABLE myTable;")
fun deleteAndResetPrimaryKey()

Add unique column to already exist sqlite table

I use SQLite database in Android app, and i want to know how to add a unique column to already created table.
by this i mean that the exist table uniqness determine by 2 values and i want to edit the table uniqueness to be determined by 3 values (the 2 exist and 1 more).
is it possible?
The writable_schema trick does not work for UNIQUE because the internal index would need to be changed.
The only way to make this change is with a temporary copy of the table:
CREATE TABLE NewTable(
[...],
UNIQUE(Col1, Col2, Col3)
);
INSERT INTO NewTable SELECT * FROM MyTable;
DROP TABLE MyTable;
ALTER TABLE NewTable RENAME TO MyTable;
(This should be wrapped in a transaction; Android's onUpdate does this automatically.)

Categories

Resources