8

I'm trying to close and then reopen Room database. (The purpose is to backup SQLite file)

This is how I close it:

public static void destroyInstance() {
    if (INSTANCE != null && INSTANCE.isOpen()) {
        INSTANCE.close();
    }
    INSTANCE = null;
}

INSTANCE is a RoomDatabase object

And to reopen I'm initializing INSTANCE object again by calling:

Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, C.ROOM_DB_NAME)

After I move to another activity, I'm seeing this error in logcat: E/ROOM: Invalidation tracker is initialized twice

SELECT queries work fine, but INSERT fails with the following errors:

E/SQLiteLog: (1) no such table: room_table_modification_log

E/ROOM: Cannot run invalidation tracker. Is the db closed?
java.lang.IllegalStateException: The database '/data/user/0/ro.example.example/databases/mi_room.db' is not open.

Although INSTANCE.isOpen() returns true

Room version: 1.1.1

Does anyone know what is with this "room_table_modification_log" table?

Alex Busuioc
  • 992
  • 1
  • 11
  • 24
  • Did you get a solution to this? I'm still getting this error in Room 2.2.5. – MickeyR Sep 13 '20 at 11:14
  • No solution. I just don't close the DB anymore. If you want to copy the DB file to another location, you can check the answer below (schv09's answer) – Alex Busuioc Sep 14 '20 at 10:05
  • @MickeyR Did you get any solution for that, I am also getting this error – Muazzam A. Nov 27 '21 at 19:52
  • Any solution for that @AlexBusuioc I am getting this error in version 2.3.0 – Muazzam A. Nov 28 '21 at 14:05
  • 1
    No solution for reopening a previously closed room database. I'm just not closing the DB anymore. It might help to check @schv09's answer or this question: https://stackoverflow.com/questions/50987119/backup-room-database – Alex Busuioc Nov 28 '21 at 16:46
  • @MuazzamA. This was the solution for me: https://stackoverflow.com/questions/63957123/room-db-file-import-error-no-such-table-room-table-modification-log/64054341#64054341 – MickeyR Nov 29 '21 at 19:11

3 Answers3

4

For future readers: You don't need to close the database to copy the file to another location (create a backup).

The Android implementation for a SQLite database is normally in WAL (write-ahead log) mode. This uses 3 files in the background: The first with the name of your database (i.e. my_db, the second one with that name and the "-shm" suffix (my_db-shm) and the third one with the "-wal" suffix (my_db-wal). The -wal file will save changes.

If you want to make a backup using the normal path for your database (my_db file), you need to make sure that it is up to date with the latest transactions. You do this by running a checkpoint on the database. After this, you can copy this file to the desired location on the phone and then keep using your database without issues. The accepted answer on this question explains it well:

But if moving everything to the original database file is what you want to do, then you don't have to close the database.

You can force a checkpoint using the wal_checkpoint pragma instead. Query the following statement against the database. We use raw queries here as pragma is not yet supported by Room (it will trigger a UNKNOWN query type error).

Have this query inside of your DAO:

@RawQuery
int checkpoint(SupportSQLiteQuery supportSQLiteQuery);

And then when you call the checkpoint method, use the query then:

myDAO.checkpoint(new SimpleSQLiteQuery("pragma wal_checkpoint(full)"));
schv09
  • 995
  • 1
  • 9
  • 15
  • Great answer! But what will happen, if I want to clear current database after checkpoint and to copy database base file to storage? I use rxjava - and are there no guarantees, that some thread should not write something to database between 'checkpoint' and 'copying DB' functions, as result, I'm not sure, that I don't clean data which don't moved to storage. What do you think? Thank! – dr_begemot Nov 21 '20 at 11:01
  • UPD: I think, I should be using this operations in beginTransaction() and endTransaction() block, without DAO, using raw 'query' (because DAO wrap everything in begin/endTransaction() - and it's not true in this situation), what do you thing, I'm right? – dr_begemot Nov 21 '20 at 11:02
  • I'm sorry, dr_begemot, but I can't help you here since I don't use rxjava. I hope you find a solution soon! – schv09 Nov 22 '20 at 05:12
  • 1
    @schv09 I know this is an old post, but is there a way to reset the database instance without having to close the app and reopen it? when I restore the database, all my flows are still using an old instance of the database and I have to close the open and remove it from recents and open it again to get the restored data. – Martin Dec 23 '22 at 22:22
  • @Martin, did you find a solution for this? – Captain Jacky Jul 11 '23 at 15:45
0

Downgrade your Room version to 1.1.1-rc1 and the problem will be gone. Keep an eye out on updates as this is a bug in 1.1.1

Zun
  • 1,553
  • 3
  • 15
  • 26
  • Just tested. Nope, this does not solve the issue. Exactly the same behaviour – Alex Busuioc Jul 19 '18 at 11:29
  • I remember having the same issues. This post fixed the issue for me and other people https://stackoverflow.com/questions/50370683/room-migration-no-such-table-room-table-modification-log – Zun Jul 19 '18 at 11:31
  • Indeed, these two issues seem to be related. The context in which they appear are different (close - reopen database in my case; database migration in the other post's case). Unfortunately, my issue manifests on all Room versions – Alex Busuioc Jul 19 '18 at 11:52
0

After spending so many days finally I found the solution to this, you need to create the table room_table_modification_log in onOpen Db callback.

Like below:

private fun buildDatabase(context: Context): MainDatabase {
            return Room.databaseBuilder(
                context.applicationContext,
                MainDatabase::class.java,
                databaseName
            ).addMigrations(MIGRATION_2_3)
                .addCallback(getCallback())
                .build()
        }

And implement the getCallback() function like this:

fun getCallback(): Callback {
    return object : Callback() {
        override fun onOpen(db: SupportSQLiteDatabase) {
            super.onOpen(db)
                db.execSQL("CREATE TEMP TABLE room_table_modification_log(table_id INTEGER PRIMARY KEY, invalidated INTEGER NOT NULL DEFAULT 0)")          
            }
        }
}

After doing the above things you will not able to get the error room_table_modification_log

Muazzam A.
  • 647
  • 5
  • 17