Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Good schema to delete database file in SQLiteOpenHelper.onDowngrade()

I have an existing database based on SQLiteOpenHelper that has several versions and code to upgrade it and that works fine. But in case a user installs an older version of the app (that expects a lower database version) it will currently crash - the ContentProvider using it can't access the database. I'd like to prevent it from crashing but I don't want to actually downgrade the database - adding the code to do that would be pain. Dropping all tables would certainly work but starting with a fresh file is imo cleaner and less error prone.

That's about what the database helper looks like - nothing special

public class MyDbHelper extends SQLiteOpenHelper {
    private static final int DATABASE_VERSION = 3;
    private static final String DATABASE_NAME = "my.db";

    public MyDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        onUpgrade(db, 0, DATABASE_VERSION);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if (newVersion < 1) db.execSQL("CREATE TABLE A...");
        if (newVersion < 2) db.execSQL("CREATE TABLE B...");
        if (newVersion < 3) db.execSQL("CREATE TABLE C...");
    }

    @Override
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // I'd like to delete the database here
        // the only problem is that I can't from here
        // since this is called in the middle of getWritableDatabase()
        // and SQLiteDatabase has no .recreate() method.
    }
}

The possible ways I've come up to do that are:

  • Do it from the outside: catch exceptions in the ContentProvider, delete the file and request to open the database again. - I don't like that since it's not the responsibility of the provider.
  • Replacing SQLiteOpenHelper with my own copy of that class that deletes the file instead of calling onDowngrade - Problem is that it's using package private parts of SQLiteDatabase (e.g. .lock()) which I can't replace without duplicating SQLiteDatabase too (that would probably result in duplicating the whole sqlite stack).

Is there any good approach to do that or do I have to go the DROP TABLES way e.g. like described here?

like image 463
zapl Avatar asked Mar 20 '12 13:03

zapl


1 Answers

I've figured out a way that works nicely by extending SQLiteOpenHelper and all I need to do in MyDbHelper is to extend this class.

public abstract class DeletingSQLiteOpenHelper extends SQLiteOpenHelper {
    private static final String TAG = DeletingSQLiteOpenHelper.class.getSimpleName();

    private final File mDatabaseFile;

    public DeletingSQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
            DatabaseErrorHandler errorHandler) {
        super(context, name, factory, version, errorHandler);
        mDatabaseFile = context.getDatabasePath(name);
    }

    public DeletingSQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
        super(context, name, factory, version);
        mDatabaseFile = context.getDatabasePath(name);
    }

    @Override
    public synchronized SQLiteDatabase getWritableDatabase() {
        try {
            return super.getWritableDatabase();
        } catch (SQLiteDowngradeFailedException e) {
            // that's our notification
        }

        // try to delete the file
        mDatabaseFile.delete()

        // now return a freshly created database
        return super.getWritableDatabase();
    }

    @Override
    public final void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // throwing a custom Exception to catch it in getWritableDatabase
        throw new SQLiteDowngradeFailedException();
    }

    // that's the exception
    static class SQLiteDowngradeFailedException extends SQLiteException {
        public SQLiteDowngradeFailedException() {}

        public SQLiteDowngradeFailedException(String error) {
            super(error);
        }
    }
}
like image 86
zapl Avatar answered Sep 19 '22 16:09

zapl