I have a situation where I want to be able to do a hard reset of my database using Android Room. Using SQLiteOpenHelper, I could do this by writing a method to drop all tables and indices, then manually call SQLiteOpenHelper.onCreate().
I'm aware that I can get access to a SupportSQLiteOpenHelper via room and drop all the tables myself, but there doesn't seem to be a good way to kick-off the recreation process.
Also, I'm aware that I could delete every item from each table without dropping it, but that's not what I want. That doesn't reset the auto-incrementing primary key, so the "id" field of new items won't reset back to 1.
Thanks!
EDIT:
This is something I want to be able to do arbitrarily at runtime.
EDIT 2:
The method should be maintainable, i.e. not involve hand-writing SQL that matches Room's behavior. Ideally there would be some way to retrieve the SQL that Room generates, or a SQLiteOpenHelper.onCreate() equivalent method. Or anything else that solves this problem! :)
I found it easiest to do this via context.deleteDatabase(“name”) and then simply reinstantiating and repopulating the database via the Room.databaseBuilder().addCallback upon first access.
Simple answer is to increment your #Database version number. Generally speaking you should do this for schema changes, however it will achieve your aims of clearing the database and resetting all primary key values.
#Database(entities = { Hello.class }}, version = 1) // <- you want to increase version by 1
#TypeConverters({})
public abstract class AppDatabase extends RoomDatabase {
public abstract HelloDao helloTextDao();
}
EDIT: IF you want to do this at run time, I would clear all the data from your tables (to avoid FK issues), then call DROP TABLE table_name, on all of your respective tables. Then you will need to write queries for creating the tables ie : CREATE TABLE table_name (uid INTEGER PRIMARY KEY, name STRING);.
This will mean you have to maintain a list of create table queries up to date with your POJO's unfortunatly. Ideally you'd be able to use reflection to generate the query, howerever #ColumnInfo currently doesn't support reflection as it has its RetentionPolicy set to CLASS.
Hopefully this solves your problem, feel free to ask for further clarification in the comments. Happy hunting!
For my case, deleting database with context.deleteDatabase(“name”) still caches the data of the old database.
I solved this by manually deleting the database file
File databasesDir = new File(context.getApplicationInfo().dataDir + "/databases");
new File(databasesDir, "MyDatabaseName.db").delete();
I was not able to find a way to do this programatically.
But, you can go to yourApp -> Long click -> App Info -> Storage & Cache -> clear both cache and Storage.
Clearing storage displays a verification dialog that indicates that databases will be destroyed.
This may help you.
upgrade your Database version
When you call DatabaseBuilder, remember add this to your code fallbackToDestructiveMigration
Room.databaseBuilder(context, Database::class.java, DB_NAME)
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build()
More detail please refer this
As per the answers given here, and here, and here, I could do it as following:
public void deleteAndReset() {
SQLiteDatabase database;
database = SQLiteDatabase.openOrCreateDatabase(context.getDatabasePath(MY_DATABASE_NAME), null);
String deleteTable = "DELETE FROM " + MY_TABLE_NAME;
database.execSQL(deleteTable);
String deleteSqliteSequence = "DELETE FROM sqlite_sequence WHERE name = '" + MY_TABLE_NAME + "'";
database.execSQL(deleteSqliteSequence);
}
Related
Hi I am developing an android app and I am using room DB and I have created one table and now I want to add another table I need to write a migration for that and in-migration I need to write full SQL queries for the new table assume I have more than 20 fields how complex will be the query.
In SQLite, we need to write such complex queries which sometimes becomes complicated to write and find errors So, room DB came to resue but now we need to do the same in the room DB migration. How does it useful then?
If we use SQLite, then if we already added one table and now We want to add another table then we can just uninstall the application and new tables will be generated, but in-room DB this is not a case I tried the same thing but it is still showing me that you need to write a migration for the new table. So, in this case, while developing there will a lot of migration scripts that will be hard to maintain at some point in the future.
How does it useful then I have to write a multiple create queries while developing the app this is a very basic flow in any application.
Once we go to prodution then it makes sense to write migration for every table but not in the developing mode.
How does room DB make developr job eaiser?
I have more than 20 fields how complex will be the query.
It can be very simple as an Entity defines an Object e.g. your 20 columns and to get the 20 columns can be as simple as
#Query(SELECT * FROM thetable)
List<Thetable> getAll();
The above being in an Interface that is annotated with #Dao and all you do in the code is retrieve an instance from the built room database and then use the getAll method which returns a List of Thetable objects. Each with all the member variables populated from the database.
e.g. you could have :-
mMyTheTableDaoObject = mMyBuiltRoomDatabase.getAll();
List<TheTable> myTheTableList = mMyTheTableDaoObject.getAll();
for(TheTable t: myTheTableList) {
int current???? = t.get????();
}
While using standard/non-room then you would have to do something along the lines of :-
SQLitedatabase db = whatever_you_need_to_do_to_get_an_SQLiteDatabase_instance;
Cursor c = db.query("theTable",null,null,null,null,null,null);
ArrayList<TheTable> myTheTableList = new ArrayList();
while(c.moveToNext()) {
currentTheTable = new TheTable();
current.TheTable.setId = c.getLong(c.getColumnIndex("id");
current.TheTable.setNextColumn1 = c.getString("next_column1");
current.TheTable.setNextColumn2 = c.getString("next_column2");
........ another 17 similar lines of code
currentTheTable.setNextColumn20 = c.getString("next_column20");
myTheTableList.add(currentTheTable);
}
for(TheTable t: myTheTableList) {
int current???? = t.get????();
}
If we use SQLite, then if we already added one table and now We want to add another table then we can just uninstall the application and new tables will be generated, but in-room DB this is not a case I tried the same thing but it is still showing me that you need to write a migration for the new table.
Once we go to production then it makes sense to write migration for every table but not in the developing mode.
Rather then migrating simply delete the database (delete the App's data or uninstall the App, the database is stored in the default location (data/data/package_name/databases)) and rerun without changing the version. The database will be created as per the new schema. Perhaps utilising temporary code to load data accordingly.
How does room DB make developr job eaiser?
In Short ROOM generates what is termed as the boilerplate code from relatively simple code e.g the #Query above writes the underlying code to extract the data and build the objects (e.g. the code as above).
Please check the official document:
https://developer.android.com/training/data-storage/room/migrating-db-versions
Actually Room using SQLITE behind the scene. It provide you plethora of other facilities. In case of Migration you have to write full code to create table.
Harsh your question is valid in some way but as you know android is maintaining SQLite database and libraries like room, greendao or even native SQLiteOpenHelper
is handling the transaction with sqllite behind the scene for the developers.
In all the earlier libraries too you have to maintain versions of your database and which fields or tables have been added to your database and write migrations for the version upgrades of the database.
The beauty of room comes in play in how easy they have made the CRUD operations on the SQLite database and getting data wrapped in LiveData or Observable, not that you don't need to write migrations.
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.
Is there any way I can create and drop tables similar to a 'RawQuery'?
I tried with a #RawQuery annotation (which it would be the perfect solution for me) but when I am compiling I get an error saying methods annotated with RawQuery can't return void.
I read only SELECT, UPDATE and DELETE statements are allowed when using #Query.
I would like to achieve the "creation or deletion of tables" by passing a tablename as a parameter, something like the following:
#Query("DROP TABLE :name")
void deleteTable (String name);
Any ideas on how to achieve this?
Thanks!
Official doc states that,
RawQuery serves as an escape hatch where you can build your own SQL query at runtime but still use Room to convert it into
objects.
RawQuery methods must return a non-void type. If you want to execute a raw query that does not return any value, use
RoomDatabase#query methods.
or use it like,
#RawQuery
int deleteTable (SupportSQLiteQuery query); //We can return int status like it used to return with database.delete()
//Usage
dao.deleteTable(
new SimpleSQLiteQuery("DROP TABLE tablename")
)
The ting is, wit Room, you don't have to "drop" tables, the tables re created based on your entity classes (annotated with #Entity).
As far as I know, you usually need to drop tables in case the columns change or there are some updates on the "structure", with Room there's no point in doing this unless you change the structure of your entity that can't be automatically handled by the migration. In this case, Room gives you the chance to do the migration by yourself. Check the documentation here: https://developer.android.com/training/data-storage/room/migrating-db-versions
But like the documentation states, be really careful with this.
Note that I come from a .NET background where I'm familiar with Entity Framework.
So in EF migrations, the actual migrations are generated for you. Basically, you specify in your POCO objects the changes you want. When you add a new migration, it does a diff between the database and the POCO objects and generates a migration script. You barely have to even look at it.
So using Android Room, I'm finding I have to handcraft these migrations myself. And there are rules that mean the system crashes unless the migration you handcraft puts the schema in exactly the state Room thinks it should be based on the POJO objects.
For me, this seems like a tedious and risky task that could be automated (like EF migrations already does). So my question is, is there a tool or something that'll do these migrations automatically? If not, what are some guidelines on how to do these migrations safely or efficently?
I'm not sure if a tool like this exists specifically for Room as of today, but I hardly doubt it, since Room's stable version was released just a few months ago.
One small thing you can get generated is SQL statements for tables creation.
Just add:
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
to android/defaultConfig in your's build.gradle and a JSON with complete schemas will be generated.
To do these migrations safely you can fully test them - this is well described here
Regarding efficiency I would recommend creating direct migrations like Migration(1, 4)
The android room migrations are problematic, because even a difference of column order will show a failed migration. But due to sqlite's limitations column type alterations and so forth cannot be done. Do as in Michal's answer and generate the schemas.
Then use a JSON diff tool to see how it generates your particular table.
If the difference is easy, do the necessary alter command in your migration script. I've pretty much given up on doing this and instead just create a new table the way my new version expects it to be, copy the data from the old table, drop it, and rename the correctly created table. Here's some code:
static final Migration MIGRATION_3_4 = new Migration(3, 4) {
#Override
/**
* Instead of trying to read the owl droppings of the error message, simply go to the schemas directory
* and do a table copy of the before and after. Look for the table creation in the updated schema number
* then do an insert by doing a select * from the old
* then remove the old and rename the new
*/
public void migrate(SupportSQLiteDatabase _db) {
_db.execSQL("CREATE TABLE IF NOT EXISTS crew2 (`crewId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `shortName` TEXT, `isCaptain` TEXT)");
_db.execSQL("INSERT INTO crew2(crewid,name,shortname) SELECT * FROM crew_table");
_db.execSQL("DROP TABLE crew_table");
_db.execSQL("ALTER TABLE crew2 RENAME TO crew_table");
_db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_crew_table_shortName` ON `crew_table` (`shortName`)");
}
};
I use Sugar ORM 1.5 in my app and I want to clear all info when the user logoff.
I want to avoid deleting the database file or use pure SQLite scripts if possible.
The only solution I found was using deleteAll on all tables, but I want to know if there is a better way to do it.
Regards,
EDIT
I solve the problem I had deleting the database just calling SugarContext.terminate(); before deleting the database and SugarContext.init(context); after.
Looks like it's the best solution like Henry Dang pointed in the comments, and it is faster than deleting all data.
This snippet works for me. It terminates the context deletes all tables and recreates them:
SugarContext.terminate();
SchemaGenerator schemaGenerator = new SchemaGenerator(getApplicationContext());
schemaGenerator.deleteTables(new SugarDb(getApplicationContext()).getDB());
SugarContext.init(getApplicationContext());
schemaGenerator.createDatabase(new SugarDb(getApplicationContext()).getDB());
SchemaGenerator is from com.orm, my SugarORM version is 1.5.
for other users who just want to delete all records (not tables or database)
public static void deleteAllrecords(Context applicationContext) {
List<Class> domainClasses = getDomainClasses(applicationContext);
for (Class domain : domainClasses) {
SugarRecord.deleteAll(domain);
}
}
if we want to delete sequence also you can add this line inside for loop
SugarRecord.executeQuery("DELETE FROM SQLITE_SEQUENCE WHERE NAME = '" + NamingHelper.toSQLName(domain) + "'");