Room database migration with missing 1.json schema file - android

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.

Related

createFromAsset and fallbackToDestructiveMigration methods of Room library - no data exists

I have a .db file in assets folder. My RoomDatabase class is as the following. I install the app to my device. Then I changed version = 2 in the below class, and make my prepopulated database version 2. Then i renamed one of the columns in my table so that schema is changed. Then i installed the app again. And boom! Room gives me the following error :
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.
#Database(entities = [Word::class], version = 1, exportSchema = false)
abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
companion object {
#Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context): WordRoomDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
}
private fun buildDatabase(ctx: Context): WordRoomDatabase {
val passphrase: ByteArray = SQLiteDatabase.getBytes("my password".toCharArray())
val factory = SupportFactory(passphrase)
return Room.databaseBuilder(ctx.applicationContext, WordRoomDatabase::class.java, "word_database2.db")
.openHelperFactory(factory)
.createFromAsset("word_database.db")
.fallbackToDestructiveMigration()
.build()
}
}
}
After this point, I completely deleted my app. Make the prepopulated database version and #Database version 1. I made allowBackup false in AndroidManifest.xml. So there is no database exist in my device. But I am still facing the same error when I install the app to device. What a nonsense error is this. How can I solve it ?
I faced the same issue in the near past. I'm quoting from http://www.sqlite.org/lang_altertable.html
SQLite supports a limited subset of ALTER TABLE. The ALTER TABLE command in SQLite allows the user to rename a table or to add a new column to an existing table. It is not possible to rename a colum, remove a column, or add or remove constraints from a table.
Alternatively, you can create a new table and fill the new table with data of old table.
One last and the most important point I want to say about is that if you want to change the schema of .db file exist in assets folder, then you should do that with sqlite command line. When I try to change schema by using sqlite db browser, it does not work like you said. There is a problem in sqlite db browser for changing schema.
So , if you want to change schema, do it in sqlite command line.
Increase .pragma version in both code and .db(You can increase .pragma using db browser for sqlite).
For adding removing adding data without changing schema purposes, you can safely continue to work with sqlite db browser.

Room Database schema update without data loss

I have developed one application in Android using Kotlin and it is available on playstore. I have used Room database to store the values. I have following queries:
Database schema is changed now, How to I need to handle that. I referred to below tutorial but still not clear to handle the schema change in Migration.
Visit https://developer.android.com/training/data-storage/room/migrating-db-versions.html
How can I test my current application with playstore version?
Thanks for the help in advance.
That's quite a complex question but basically you have 2 strategies:
fallbackToDestructiveMigration -> simple to implement but your users will lose their data once the app is updated
Provide a Migrationstrategy (preferable)
Case 1 - fallbackToDestructiveMigration
In your database initialization, simply invoke fallbackToDestructiveMigration on your database builder:
database = Room.databaseBuilder(context.getApplicationContext(),
UsersDatabase.class, "Sample.db")
.fallbackToDestructiveMigration()
.build();
In this case, since you have updated your database version (suppose from version 1 to version 2) Room can't find any migration strategy, so it will fallback to distructive migration, tables are dropped.
Case 2 - Smart migration
Suppose you have a table called "Users" and suppose you added a column to this table in version 2 of your database. Let's call this column "user_score" You should implement migrate interface of Migration class in order to update your "Users version 1" schema to "Users version 2" schema. To do so, you need an alter table, you can write it directly inside the migrate method:
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(SupportSQLiteDatabase database) {
// Your migration strategy here
database.execSQL("ALTER TABLE Users ADD COLUMN user_score INTEGER")
}
};
database = Room.databaseBuilder(context.getApplicationContext(),
UsersDatabase.class, "Sample.db")
.addMigrations(MIGRATION_1_2)
.build();
More references here :
https://medium.com/androiddevelopers/understanding-migrations-with-room-f01e04b07929
https://medium.com/#elye.project/android-sqlite-database-migration-b9ad47811d34
From room version 2.4.0, you can easily update using autoMigrations.
DATABASE CLASS
#Database(
version = 3,
autoMigrations = [
AutoMigration(from = 1, to = 2),
AutoMigration(from = 2, to = 3)
],
.....
)
DATA CLASS
#Entity(tableName = "user")
data class DataUser(
....
// I added this column, like this
#ColumnInfo(defaultValue = "")var test: String = ""
)
see reference below
android developer: room version2.4.0
android developer: autoMigration
For your question 2, you can do the following things:
Download and install apk file in mobile device from playstore.
Build your apk file(signed apk). Before generating the apk file
don't forget to increase version code.
install the signed apk file in device by following below adb command
adb install -r "apk_file_path"
Hope this will work.
You should use a VCS (like git). For each version of your app you should create a tag, so you can easily switch between published versions to test compatibility.
The migration itself can be done by invoking raw queries on the room database in your migration. If you don't know how to write those, you should first read some tutorials about SQL.

Android Room persistance library. Drop Table

I need to know how to "DROP FROM Table" using Room Persistence Library.
I already know that we can delete all the rows using the method:
#Query("DELETE FROM table")
void deleteAll();
However, what I need is deleting the whole table. This is because of my primary_key is an autoincrement column, so using the previous code, it is not rested.
I already read answers about this topic in the next links:
Reset auto-increment in Android's Room library
Android Room - reset auto generated key on each app run
However, I can't believe that this library doesn't provide an easier way to do this, regardless of the reason or use.
Could use Migrations that Room provides for updating the database via our own queries. As we want to make changes to the database which Room cannot resolve (yet) from the code. We could delete the table, recreate it or update it. Depending on what is needed.
Option 1: Migrate with keeping other data
First increase the version of the database: update the version parameter in the #Database annotation.
Create a migration like this:
static final Migration MIGRATION_1_2 = new Migration(1, 2) { // From version 1 to version 2
#Override
public void migrate(SupportSQLiteDatabase database) {
// Remove the table
database.execSQL("DROP TABLE my_table"); // Use the right table name
// OR: We could update it, by using an ALTER query
// OR: If needed, we can create the table again with the required settings
// database.execSQL("CREATE TABLE IF NOT EXISTS my_table (id INTEGER, PRIMARY KEY(id), ...)")
}
};
Add the migration when building the database:
Room.databaseBuilder(context, MyDatabase.class, "mydatabase")
.addMigration(MIGRATION_1_2) // Add the migration
.build();
Run the app again. If the queries were correct, the migration is done
Option 2: Migrate with losing data
There is also a fast option, but all data in the database will be cleared!
This is because the database gets recreated when using the method below.
Like option one, increment the version of the database
Use .fallbackToDestructiveMigration() when creating the database, like so:
Room.databaseBuilder(context, MyDatabase.class, "mydatabase")
.fallbackToDestructiveMigration()
.build();
Run the app. It will remove the old database and recreate it. (All earlier data is wiped)
If you want to do this with auto migration then need to use spec as autoMigrations value for #Database annotation. Look like
this
autoMigrations = {#AutoMigration(from = 1, to = 2, spec = AppDatabase.MyAutoMigration.class)}
Example like you want to delete a table(YourTableName) from database version 1 and then migrate to version 2 then the full code looks like this
#Database(
version = 2,
entities = {Entity1.class, Entity2.class},
autoMigrations = {#AutoMigration(from = 1, to = 2, spec = AppDatabase.MyAutoMigration.class)},
exportSchema = true)
#TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
#DeleteTable.Entries(value = #DeleteTable(tableName = "YourTableName"))
public static class MyAutoMigration implements AutoMigrationSpec {
}
// Your DAO 1
// Your DAO 2
}

Android Room, SQLite - TO expected, got 'COLUMN'

I'm trying to rename a column in my Room database. I foolishly used the column name index and want to change it to id, but this migration function is causing headaches:
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
#Override
public void migrate(#NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE items RENAME COLUMN index TO id;");
}
};
I followed this syntax diagram for the query:
Android Studio is giving me the error TO expected, got 'COLUMN', and I cannot use the database due to the RuntimeException:
Caused by: 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.
The version number is correct, so I am assuming this issue is caused by the syntax problem above; I cannot find anything else wrong with my setup.
Android uses SQLite v3.19. That makes renaming a column using RENAME COLUMN not possible. The best approach is to recreate the table. – Leo Pelozo
Looks like I need to create function that drops the table so I can create a new one.
#Query("DELETE FROM items")
void dropTable();
... then create the table again, though I'm not sure how to go about this.
Update:
I was able (I think, we'll see...) to re-create the table by calling the above function, removing ALL migrations and setting the database version back to 1. Then I re-defined the database class itself with the proper names etc. and was able to insert data into it without any errors. Adding .fallbackToDestructiveMigration() to my database singleton class was also necessary.
Personally I think this is a little ridiculous just for simply re-naming a column; I was never able to simply rename the column and add a migration for the change, nor was I able to drop the table and re-create it with the proper column name and add that as a migration. But alas, this is Android after all.

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.

Categories

Resources