From 8b54c6e5c40c06269af76694b5b20a5c35bf341c Mon Sep 17 00:00:00 2001 From: cpfeiffer Date: Fri, 10 Jul 2015 00:31:45 +0200 Subject: [PATCH] New incremental, cumulative database upgrades and downgrades --- .../database/ActivityDatabaseHandler.java | 102 ++++++++++-------- .../gadgetbridge/database/DBConstants.java | 14 +++ .../gadgetbridge/database/DBHelper.java | 20 ++++ .../gadgetbridge/database/DBUpdateScript.java | 16 +++ .../schema/ActivityDBCreationScript.java | 21 ++++ .../database/schema/ActivityDBUpdate_4.java | 32 ++++++ 6 files changed, 159 insertions(+), 46 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBConstants.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBUpdateScript.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBCreationScript.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBUpdate_4.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java index c41052d2..c049ff79 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java @@ -5,76 +5,86 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.widget.Toast; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import nodomain.freeyourgadget.gadgetbridge.GBActivitySample; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.database.schema.ActivityDBCreationScript; + +import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.*; public class ActivityDatabaseHandler extends SQLiteOpenHelper { + private static final Logger LOG = LoggerFactory.getLogger(ActivityDatabaseHandler.class); + private static final int DATABASE_VERSION = 5; - private static final String DATABASE_NAME = "ActivityDatabase"; - - private static final String TABLE_GBACTIVITYSAMPLES = "GBActivitySamples"; - - private static final String KEY_TIMESTAMP = "timestamp"; - private static final String KEY_PROVIDER = "provider"; - private static final String KEY_INTENSITY = "intensity"; - private static final String KEY_STEPS = "steps"; - private static final String KEY_TYPE = "type"; - public ActivityDatabaseHandler(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { - String CREATE_GBACTIVITYSAMPLES_TABLE = "CREATE TABLE " + TABLE_GBACTIVITYSAMPLES + " (" - + KEY_TIMESTAMP + " INT," - + KEY_PROVIDER + " TINYINT," - + KEY_INTENSITY + " SMALLINT," - + KEY_STEPS + " TINYINT," - + KEY_TYPE + " TINYINT," - + " PRIMARY KEY (" + KEY_TIMESTAMP + "," + KEY_PROVIDER + ") ON CONFLICT REPLACE)" + getWithoutRowId(); - db.execSQL(CREATE_GBACTIVITYSAMPLES_TABLE); - } - - /** - * WITHOUT ROWID is only available with sqlite 3.8.2, which is available - * with Lollipop and later. - * - * @return the "WITHOUT ROWID" string or an empty string for pre-Lollipop devices - */ - private String getWithoutRowId() { - if (GBApplication.isRunningLollipopOrLater()) { - return " WITHOUT ROWID;"; + try { + ActivityDBCreationScript script = new ActivityDBCreationScript(); + script.createSchema(db); + } catch (RuntimeException ex) { + LOG.error("Error creatomg database", ex); + Toast.makeText(GBApplication.getContext(), "Error creating database.", Toast.LENGTH_SHORT).show(); } - return ""; } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (newVersion == 5 && (oldVersion == 4 || oldVersion == 3)) { - String CREATE_NEW_GBACTIVITYSAMPLES_TABLE = "CREATE TABLE NEW (" - + KEY_TIMESTAMP + " INT," - + KEY_PROVIDER + " TINYINT," - + KEY_INTENSITY + " SMALLINT," - + KEY_STEPS + " TINYINT," - + KEY_TYPE + " TINYINT," - + " PRIMARY KEY (" + KEY_TIMESTAMP + "," + KEY_PROVIDER + ") ON CONFLICT REPLACE)" + getWithoutRowId(); - db.execSQL(CREATE_NEW_GBACTIVITYSAMPLES_TABLE); - db.execSQL("insert into NEW select timestamp,provider,intensity,steps,type from " + TABLE_GBACTIVITYSAMPLES + ";"); - db.execSQL("Drop table " + TABLE_GBACTIVITYSAMPLES + ";"); - db.execSQL("alter table NEW RENAME TO " + TABLE_GBACTIVITYSAMPLES + ";"); - } else { - //FIXME: do not just recreate - db.execSQL("DROP TABLE IF EXISTS " + TABLE_GBACTIVITYSAMPLES); - onCreate(db); + try { + for (int i = oldVersion + 1; i <= newVersion; i++) { + DBUpdateScript updater = getUpdateScript(db, i); + if (updater != null) { + LOG.info("upgrading activity database to version " + i); + updater.upgradeSchema(db); + } + } + LOG.info("activity database is now at version " + newVersion); + } catch (RuntimeException ex) { + LOG.error("Error upgrading db version. ", ex); + Toast.makeText(GBApplication.getContext(), "Error upgrading database.", Toast.LENGTH_SHORT).show(); + throw ex; // reject upgrade } } + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + try { + for (int i = oldVersion; i >= newVersion; i--) { + DBUpdateScript updater = getUpdateScript(db, i); + if (updater != null) { + LOG.info("downgrading activity database to version " + (i - 1)); + updater.downgradeSchema(db); + } + } + LOG.info("activity database is now at version " + newVersion); + } catch (RuntimeException ex) { + LOG.error("Error downgrading db version. ", ex); + Toast.makeText(GBApplication.getContext(), "Error downgrading database.", Toast.LENGTH_SHORT).show(); + throw ex; // reject downgrade + } + } + + private DBUpdateScript getUpdateScript(SQLiteDatabase db, int version) { + try { + Class updateClass = getClass().getClassLoader().loadClass(getClass().getPackage().getName() + ".schema.ActivityDBUpdate_" + version); + return (DBUpdateScript) updateClass.newInstance(); + } catch (ClassNotFoundException e) { + return null; + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException("Error instantiating DBUpdate class for version " + version, e); + } + } public void addGBActivitySample(GBActivitySample GBActivitySample) { try (SQLiteDatabase db = this.getWritableDatabase()) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBConstants.java new file mode 100644 index 00000000..696f30d7 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBConstants.java @@ -0,0 +1,14 @@ +package nodomain.freeyourgadget.gadgetbridge.database; + +public class DBConstants { + public static final String DATABASE_NAME = "ActivityDatabase"; + + public static final String TABLE_GBACTIVITYSAMPLES = "GBActivitySamples"; + public static final String TABLE_STEPS_PER_DAY = "StepsPerDay"; + + public static final String KEY_TIMESTAMP = "timestamp"; + public static final String KEY_PROVIDER = "provider"; + public static final String KEY_INTENSITY = "intensity"; + public static final String KEY_STEPS = "steps"; + public static final String KEY_TYPE = "type"; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java index ff445285..54d41662 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java @@ -11,6 +11,7 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; public class DBHelper { @@ -64,4 +65,23 @@ public class DBHelper { } } } + + public static void dropTable(String tableName, SQLiteDatabase db) { + db.delete(tableName, null, null); + } + + /** + * WITHOUT ROWID is only available with sqlite 3.8.2, which is available + * with Lollipop and later. + * + * @return the "WITHOUT ROWID" string or an empty string for pre-Lollipop devices + */ + public static String getWithoutRowId() { + if (GBApplication.isRunningLollipopOrLater()) { + return " WITHOUT ROWID;"; + } + return ""; + } + + } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBUpdateScript.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBUpdateScript.java new file mode 100644 index 00000000..9e696ed4 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBUpdateScript.java @@ -0,0 +1,16 @@ +package nodomain.freeyourgadget.gadgetbridge.database; + +import android.database.sqlite.SQLiteDatabase; + +/** + * Interface for updating a database schema. + * Implementors provide the update from the prior schema + * version to this version, and the downgrade from this schema + * version to the next lower version. + * + * Implementations must have a public, no-arg constructor. + */ +public interface DBUpdateScript { + void upgradeSchema(SQLiteDatabase database); + void downgradeSchema(SQLiteDatabase database); +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBCreationScript.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBCreationScript.java new file mode 100644 index 00000000..c85b6163 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBCreationScript.java @@ -0,0 +1,21 @@ +package nodomain.freeyourgadget.gadgetbridge.database.schema; + +import android.database.sqlite.SQLiteDatabase; + +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript; + +import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.*; + +public class ActivityDBCreationScript { + public void createSchema(SQLiteDatabase db) { + String CREATE_GBACTIVITYSAMPLES_TABLE = "CREATE TABLE " + TABLE_GBACTIVITYSAMPLES + " (" + + KEY_TIMESTAMP + " INT," + + KEY_PROVIDER + " TINYINT," + + KEY_INTENSITY + " SMALLINT," + + KEY_STEPS + " TINYINT," + + KEY_TYPE + " TINYINT," + + " PRIMARY KEY (" + KEY_TIMESTAMP + "," + KEY_PROVIDER + ") ON CONFLICT REPLACE)" + DBHelper.getWithoutRowId(); + db.execSQL(CREATE_GBACTIVITYSAMPLES_TABLE); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBUpdate_4.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBUpdate_4.java new file mode 100644 index 00000000..8dd19ce9 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBUpdate_4.java @@ -0,0 +1,32 @@ +package nodomain.freeyourgadget.gadgetbridge.database.schema; + +import android.database.sqlite.SQLiteDatabase; + +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript; + +import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES; +import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_STEPS_PER_DAY; + +/** + * Upgrade and downgrade with DB versions <= 5 is not supported. + * Just recreates the default schema. Those GB versions may or may not + * work with that, but this code will probably not create a DB for them + * anyway. + */ +public class ActivityDBUpdate_4 extends ActivityDBCreationScript implements DBUpdateScript { + @Override + public void upgradeSchema(SQLiteDatabase db) { + recreateSchema(db); + } + + @Override + public void downgradeSchema(SQLiteDatabase db) { + recreateSchema(db); + } + + private void recreateSchema(SQLiteDatabase db) { + DBHelper.dropTable(TABLE_GBACTIVITYSAMPLES, db); + createSchema(db); + } +}