Paul Nicholas Paul Nicholas - 24 days ago 12
Android Question

Upgrading Android application data with multiple tables (without destroying unaffected tables)

In my Android application; I have a single database with multiple tables.
Each table is more-or-less separate from each other, but figured (for best practice?) to just have one DB file.

When it comes to Upgrades, it's currently an all or nothing affair. On upgrade, it "DROP"'s all the tables and re-creates them. However, this is rather harsh if only one of the tables has changed as all the other tables' data is also lost.

Is there a built-in way to auto-upgrade just the tables that have changed?
(e.g. using a version number per/table?)

If not, I guess I can see two options:


  1. Use separate databases/files for each table, to use built-in version upgrade functionality.

  2. Use the database version number to know when the "schema" has changed, but have a separate table to store the current TABLE_VERSIONS and manage my own upgrade by checking the version number of each table against the current build and DROP/CREATE Tables where needed.



(I'd rather not re-invent the wheel here, so I'm hoping I'm missing something simple...)

Answer

You need an abstract class that implements the upgrade process described here. Then you extend this abstract class for each of your tables. In your abstract class you must store you tables in a way(list, hardcoded) so when the onUpgrade fires you iterate over the table items and for each table item you do the described steps. They will be self upgraded, keeping all their existing details. Please note that the onUpgrade event fires only once per database, that's why you need to iterate over all your tables to do the upgrade of all of them. You maintain only 1 version number over all the database.

  • beginTransaction
  • run a table creation with if not exists (we are doing an upgrade, so the table might not exists yet, it will fail alter and drop)
  • put in a list the existing columns List<String> columns = DBUtils.GetColumns(db, TableName);
  • backup table (ALTER table " + TableName + " RENAME TO 'temp_" + TableName)
  • create new table (the newest table creation schema)
  • get the intersection with the new columns, this time columns taken from the upgraded table (columns.retainAll(DBUtils.GetColumns(db, TableName));)
  • restore data (String cols = StringUtils.join(columns, ","); db.execSQL(String.format( "INSERT INTO %s (%s) SELECT %s from temp_%s", TableName, cols, cols, TableName)); )
  • remove backup table (DROP table 'temp_" + TableName)
  • setTransactionSuccessful

(This doesn't handle table downgrade, if you rename a column, you don't get the existing data transfered as the column names do not match).

.

public static List<String> GetColumns(SQLiteDatabase db, String tableName) {
    List<String> ar = null;
    Cursor c = null;
    try {
        c = db.rawQuery("select * from " + tableName + " limit 1", null);
        if (c != null) {
            ar = new ArrayList<String>(Arrays.asList(c.getColumnNames()));
        }
    } catch (Exception e) {
        Log.v(tableName, e.getMessage(), e);
        e.printStackTrace();
    } finally {
        if (c != null)
            c.close();
    }
    return ar;
}

public static String join(List<String> list, String delim) {
    StringBuilder buf = new StringBuilder();
    int num = list.size();
    for (int i = 0; i < num; i++) {
        if (i != 0)
            buf.append(delim);
        buf.append((String) list.get(i));
    }
    return buf.toString();
} 
Comments