Optimize Room database migrations - android

In migration (3,4), I delete table TableA and create two new tables TableB and `TableC.
static final Migration MIGRATION_3_4 = new Migration(3, 4) {
#Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("DROP TABLE IF EXISTS `TableA`");
database.execSQL("CREATE TABLE IF NOT EXISTS " +
"`TableB` (`id` INTEGER NOT NULL, " +
"PRIMARY KEY(`id`))");
database.execSQL("CREATE TABLE IF NOT EXISTS " +
"`TableB` (`id` INTEGER NOT NULL, " +
"PRIMARY KEY(`id`))");
// bulky method
databaseWriteExecutor.execute(AppDatabase::populateProblem);
}
};
In migration (4, 5), I want to remove TableB and TableC:
static final Migration MIGRATION_4_5 = new Migration(4, 5) {
#Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("DROP TABLE IF EXISTS `TableB`");
database.execSQL("DROP TABLE IF EXISTS `TableC`");
}
};
As you can see, migration (4, 5) is the opposite of migration (3, 4). Now suppose a user wants to migrate from 3 to 5, as far as I know Room will run two successive migrations in this case 3 to 4 then 4 to 5, creating two tables and then dropping them is a waste of time, so to optimize this I add a new migration (3, 5):
static final Migration MIGRATION_3_5 = new Migration(3, 5) {
#Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("DROP TABLE IF EXISTS `TableA`");
}
};
Question: Can I safely remove migration(3, 4) from the source code? because I think it is no longer used. In this migration I am calling the populateProblem() method which is a very big method, removing the migration will reduce the size of the apk.

Yes, you can drop the intermediate migrations. You only need to keep migrations from every previous version to the latest version. So, if you latest DB version is 5, and assuming that you had versions 1 - 4 in production, you only need the following migrations:
Migration(1, 5)
Migration(2, 5)
Migration(3, 5)
Migration(4, 5)

Related

Migration not handled properly in Room

I have one db table class where I did change related to indices. Previously indices was like :
#Entity(indices = {#Index(value = {"jobNumber", "jobId"},
unique = true)})
But I changed it to
#Entity(indices = {
#Index(value = "jobNumber", unique = true), #Index(value = "jobId", unique = true)
})
But when I tried migration it's giving me issue like :
caused by: java.lang.IllegalStateException: Migration didn't properly handle Job
I need to go with second format only. I tried to add migration but seems not working. Here is my code :
public static final Migration MIGRATION_4_5 = new Migration(4, 5) {
#Override
public void migrate(SupportSQLiteDatabase database) {
migrateJobTableForIndices(database);
}
};
private static void migrateJobTableForIndices(SupportSQLiteDatabase database) {
//create new table
database.execSQL(
"CREATE TABLE Job_new (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, jobId INTEGER NOT NULL, " +
"jobNumber TEXT)");
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS index_Job_new_jobNumber_jobId ON Job_new (jobNumber, jobId)");
// Copy the data
database.execSQL(
"INSERT INTO Job_new (id, jobId, jobNumber) " +
"SELECT id, jobId, jobNumber " +
"FROM Job");
// Remove the old table
database.execSQL("DROP TABLE Job");
// Change the table name to the correct one
database.execSQL("ALTER TABLE Job_new RENAME TO Job");
}
Is there any way to add migration for updated indices format. Any reference is really appreciated
As Room generates index names. You could export the scheme to see what is happening. By adding the following to your build.gradle:
android {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
...
}
Now for the migration.
Beforehand you have to turn the foreign_keys off:
database.execSQL("PRAGMA foreign_keys=OFF;")
You have to drop the existing index. For example:
database.execSQL("DROP INDEX IF EXISTS `index_Job_jobNumber_jobId`")
Then I would suggest renaming the old table.
ALTER TABLE Job RENAME TO Job_old
Create the new table Job.
Then create the index, which you will find in the exported schema location. In this example, it is in your $projectDir/schemas. From the files there you could see and copy how Room creates the indexes and add the creation of these indexes after the table creation.
Migrate the data.
Drop the old table.
Turn the foreign_keys on:
database.execSQL("PRAGMA foreign_keys=ON;")

After creating a Trigger via Callback in Room db, the trigger is not getting created

I tried to add a trigger to limit the number of rows in my Room DB,
but the trigger is not getting created- as I see in the DB and in the generated JSON file that automatically created.
Also, migration code is not getting reached as well.
My code for creating the DB is:
#Database(entities ={Sensor.class, Meter.class, MeterHistory.class, SensorHistory.class,
ConnectivityHistory.class, GraphTypesHistory.class,
SensorSettingsHistory.class, UserInterfaceSettings.class, Temperature.class, LogSettingsHistory.class, BatteryUsage.class,
FunctionsHistory.class, MathChannelData.class}, version = 5)
#TypeConverters(UserUsageManager.DateConverter.class)
public abstract class UserUsageDB extends RoomDatabase {
public abstract UserUsageDao userUsageDao();
private static volatile UserUsageDB userUsageDBInstance;//singleton
static UserUsageDB getDatabase(final Context context) {
if (userUsageDBInstance == null) {
synchronized (UserUsageDB.class) {
if (userUsageDBInstance == null) {
userUsageDBInstance = Room.databaseBuilder(context.getApplicationContext(),
UserUsageDB.class, "user_usage_database")
.addCallback(LIMIT_CALLBACK)
.addMigrations(MIGRATION_1_2)
.fallbackToDestructiveMigration()
.build();
}
}
}
return userUsageDBInstance;
}
and the code of the migration and trigger callback:
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(SupportSQLiteDatabase database) {
// Create the new meters table
database.execSQL("CREATE TABLE meters_new ( id TEXT NOT NULL, name TEXT NOT NULL, PRIMARY KEY(id))");
// Copy the data
database.execSQL("INSERT INTO meters_new ( id, name) SELECT id, name FROM meters");
// Remove the old table
database.execSQL("DROP TABLE meters");
// Change the table name to the correct one
database.execSQL("ALTER TABLE meters_new RENAME TO meters");
// Create the new sensors table
database.execSQL("CREATE TABLE sensors_new (id TEXT NOT NULL, name TEXT NOT NULL, type TEXT NOT NULL, PRIMARY KEY(id))");
// Copy the data
database.execSQL("INSERT INTO sensors_new ( id, name) SELECT id, name FROM sensors");
// Remove the old table
database.execSQL("DROP TABLE sensors");
// Change the table name to the correct one
database.execSQL("ALTER TABLE sensors_new RENAME TO sensors");
}
};
//create a trigger to limit row count of tables
static final RoomDatabase.Callback LIMIT_CALLBACK = new RoomDatabase.Callback(){
#Override
public void onCreate(#NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
db.execSQL("Create Trigger IF NOT EXISTS Trigr_Limit_battery_usage_Delete \n" +
"AFTER Insert On battery_usage \n" +
" BEGIN \n"+
" DELETE from battery_usage\n"+
" where ts in (select ts from battery_usage order by date limit -1 offset 10);"+
" END");
db.execSQL("Create Trigger IF NOT EXISTS Trigr_Limit_sensor_settings_history_Delete \n" +
"AFTER Insert On sensor_settings_history \n" +
" BEGIN \n"+
" DELETE from sensor_settings_history\n"+
" where ts in (select ts from sensor_settings_history order by date limit -1 offset 10);"+
" END");
}
#Override
public void onOpen(#NonNull SupportSQLiteDatabase db) {
super.onOpen(db);
}
};
Can anyone help me out?
Thanks!!
but the trigger is not getting created
You are Overriding the onCreate method, this only gets called when the database is actually created (first time the App is installed). You want to Override the onOpen method to add the Triggers.
migration code is not getting reached as well.
Migration is from version 1 to 2 as per (new Migration(1, 2)), you have version 5 coded in the App as per version = 5 . Your migration will only be called when the version stored in the database is 1 and the version coded in the App is 2. You may find Understanding migrations with Room useful. Or perhaps Migrating Room databases

SQLite database migration appears to only partially apply in Espresso test

We have an SQLite database and a corresponding SQLiteOpenHelper subclass. This helper has an onDowngrade implementation that I would like to write an Espresso test for.
The full onDowngrade implementation is available here. This is a simplified version of it:
#Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("CREATE TABLE IF NOT EXISTS foo_tmp (_id integer primary key, bar text not null, baz text not null);");
db.execSQL("INSERT INTO foo_tmp(_id,bar,baz) SELECT _id,bar,baz FROM foo;");
db.execSQL("DROP TABLE IF EXISTS foo;");
db.execSQL("RENAME TABLE foo_tmp TO foo;");
}
The test loads a database dump with a very high version number and added or removed columns. It then gets a readable database and ensures that the version has been downgraded to the current expected version and that the column names are the expected column names. The full source is available here. This is what it looks like:
#Test
public void testMigration() throws IOException {
writeDatabaseFile("database" + File.separator + dbFilename);
InstancesDatabaseHelper databaseHelper = new InstancesDatabaseHelper();
SQLiteDatabase db = databaseHelper.getReadableDatabase();
assertThat(db.getVersion(), is(InstancesDatabaseHelper.DATABASE_VERSION));
List<String> newColumnNames = InstancesDatabaseHelper.getInstancesColumnNames(db);
assertThat(newColumnNames, contains(InstancesDatabaseHelper.CURRENT_VERSION_COLUMN_NAMES));
}
Everything works as intended if I manually load the same database dumps into the app. However, when I run this test, it looks like the last RENAME in the migration is not executed. If I comment out the last two SQL statements in the migration (dropping the original table and renaming the temporary table to the original table name), I can assert that the temporary table has the expected contents (here is a commit that shows this).
With some experimentation, we have found that adding databaseHelper.getReadableDatabase().close(); in the test after instantiating the SQLiteOpenHelper makes the tests pass. Given that the onDowngrade call is wrapped in a transaction, I don't understand how this is possible.
Could this point to a bug in our onDowngrade implementation? Is triggering migrations in Espresso tests different in some way?
There probably is a race condition, because SQLite is a shared resource.
eg. when the test runs before the last one COMMIT statement was issued.
Wrap it into a transaction (also see Isolation In SQLite):
if(! BuildConfig.DEBUG) {
db.beginTransaction();
} else {
db.beginTransactionWithListener(new SQLiteTransactionListener() {
#Override public void onBegin() {Log.d(LOG_TAG, "onBegin()");}
#Override public void onCommit() {Log.d(LOG_TAG, "onCommit()");}
#Override public void onRollback() {Log.d(LOG_TAG, "onRollback()");}
});
}
try {
db.execSQL("CREATE TABLE IF NOT EXISTS foo_tmp (_id integer primary key, bar text not null, baz text not null);");
db.execSQL("INSERT INTO foo_tmp(_id,bar,baz) SELECT _id,bar,baz FROM foo;");
db.execSQL("DROP TABLE IF EXISTS foo;");
db.execSQL("RENAME TABLE foo_tmp TO foo;");
db.setTransactionSuccessful();
} catch(SQLException e){
Log.d(LOG_TAG, "" + e.getMessage());
} finally {
db.endTransaction();
}
db.close();

How do I delete column from table in room database?

How do I delete column from table in room database android.I alredy try all the way drop and delete not working.
This code also not working:-
static final Migration MIGRATION_3_4 = new Migration(3, 4)
{
#Override
public void migrate(SupportSQLiteDatabase database)
{
database.execSQL("CREATE TABLE users_new (userid TEXT, username TEXT, last_update INTEGER, PRIMARY KEY(userid))");
// Copy the data
database.execSQL("INSERT INTO users_new (userid, username, last_update) SELECT userid, username, last_update FROM users");
// Remove the old table
database.execSQL("DROP TABLE users");
// Change the table name to the correct one
database.execSQL("ALTER TABLE users_new RENAME TO users");
}
};
Android ROOM is based on SQL Lite which does not allow dropping database table columns. Instead you have to:
create a temporary table without the column you are trying to delete,
copy all records from old table to new table,
drop the old table,
rename new table to same name as old table
See related question:Sqlite Dropping Column from table

How to make dynamic column using Android SQLite Database

Im writing application on Android and im using SQlite database.
I want to be able to add columns to my table by the user choice.
so the user can add any column that he wants the to table. For example the user have "animal" table and he want to add column for "dog", "cat" and "fish".
I have read about some solutions and i didnt saw one that can help me.
I read that the simple way to add column is using:
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// If you need to add a column
if (newVersion > oldVersion) {
db.execSQL("ALTER TABLE " + TableName + " ADD COLUMN " + ColumnName);
}
}
But my problem with this that i cant choose what is the name of the column that will be added to the table by the user choise, there is no parameter for string.
So i tried using something like this, and to call it directly.
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion, String newColumnName) {
// If you need to add a column
if (newVersion > oldVersion) {
db.execSQL("ALTER TABLE " + TableName + " ADD COLUMN " + newColumnName);
}
}
But i got error for this method.
I have another question about the database version.
The onUpgrade method get automaticly called when onCreate get called.
In onUpgrade there is oldVersion and newVersion parameters for the database version. when do i set the oldVersion and newVersion parameters? How i set my newVersion parameter to 2,3,4...?
You can create an auxiliary table to hold the extra column data. Queries to your primary table can be converted into queries on a new view.
create table if not exists animal (pk integer primary key, name);
create table if not exists animal_aux (animal_pk, col_name, col_val);
create view if not exists animal_view
as select animal.name as name,
ct.col_val as cat,
dt.col_val as dog
from animal, animal_aux as ct, animal_aux as dt
where animal.pk = ct.animal_pk
and animal.pk = dt.animal_pk
and ct.col_name = 'cat'
and dt.col_name = 'dog'
;
This schema should be enhanced to make animal_pk, col_name a primary key, or at least unique in animal_aux. You may also need triggers to add or remove entries in the aux table when you insert or delete entries in the animal table.
Example:
sqlite> select * from animal_view;
sqlite> insert into animal values (NULL, 'fred');
sqlite> select * from animal_view;
sqlite> select * from animal;
1|fred
sqlite> insert into animal_aux values (1, "dog", "beagle");
sqlite> insert into animal_aux values (1, "cat", "siamese");
sqlite> select * from animal_view;
fred|siamese|beagle
sqlite>
Each time you add a virtual column, you'll need to
drop view animal_view;
and then re-create it with the appropriate extra columns and where clauses.
final static String Database_name="empDb.db";
public DatabaseHelper(Context context) {
super(context, Database_name, null, 1);
}
#Override public void onCreate(SQLiteDatabase db) {
db.execSQL("create table emp_tbl (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT,salary TEXT)");
}
#Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS tbl_emp");
}
blog: https://itmulc.blogspot.com/2016/08/android-sqlite-database-with-complete.html
Get more info about it.
https://www.youtube.com/watch?v=W8-Z85oPNmQ

Categories

Resources