mirror of
https://github.com/oliexdev/openScale.git
synced 2025-08-24 09:13:04 +02:00
Introduce new database version (2)
- Change the unique constraint for measurements to include userId in addition to datetime. This way two users can now have measurements with the same date and time (hour and minute). - Enforce that the userId references an existing user.
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 2,
|
||||
"identityHash": "166a2a83c723c4117edaf1d107ac5194",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "scaleMeasurements",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userId` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `datetime` INTEGER, `weight` REAL NOT NULL, `fat` REAL NOT NULL, `water` REAL NOT NULL, `muscle` REAL NOT NULL, `lbw` REAL NOT NULL, `waist` REAL NOT NULL, `hip` REAL NOT NULL, `bone` REAL NOT NULL, `comment` TEXT, FOREIGN KEY(`userId`) REFERENCES `scaleUsers`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "userId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "enabled",
|
||||
"columnName": "enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dateTime",
|
||||
"columnName": "datetime",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "weight",
|
||||
"columnName": "weight",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "fat",
|
||||
"columnName": "fat",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "water",
|
||||
"columnName": "water",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "muscle",
|
||||
"columnName": "muscle",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lbw",
|
||||
"columnName": "lbw",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "waist",
|
||||
"columnName": "waist",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hip",
|
||||
"columnName": "hip",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "bone",
|
||||
"columnName": "bone",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "comment",
|
||||
"columnName": "comment",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_scaleMeasurements_datetime_userId",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"datetime",
|
||||
"userId"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_scaleMeasurements_datetime_userId` ON `${TABLE_NAME}` (`datetime`, `userId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "scaleUsers",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"userId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "scaleUsers",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `username` TEXT, `birthday` INTEGER, `bodyHeight` INTEGER NOT NULL, `scaleUnit` INTEGER NOT NULL, `gender` INTEGER NOT NULL, `initialWeight` REAL NOT NULL, `goalWeight` REAL NOT NULL, `goalDate` INTEGER)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userName",
|
||||
"columnName": "username",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "birthday",
|
||||
"columnName": "birthday",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bodyHeight",
|
||||
"columnName": "bodyHeight",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "scaleUnit",
|
||||
"columnName": "scaleUnit",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gender",
|
||||
"columnName": "gender",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "initialWeight",
|
||||
"columnName": "initialWeight",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "goalWeight",
|
||||
"columnName": "goalWeight",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "goalDate",
|
||||
"columnName": "goalDate",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"166a2a83c723c4117edaf1d107ac5194\")"
|
||||
]
|
||||
}
|
||||
}
|
@@ -0,0 +1,110 @@
|
||||
/* Copyright (C) 2018 Erik Johansson <erik@ejohansson.se>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package com.health.openscale;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import android.arch.persistence.db.SupportSQLiteOpenHelper;
|
||||
import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
|
||||
import android.arch.persistence.room.testing.MigrationTestHelper;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.health.openscale.core.database.AppDatabase;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNotSame;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DatabaseMigrationTest {
|
||||
private static final String TEST_DB = "migration-test";
|
||||
|
||||
@Rule
|
||||
public MigrationTestHelper helper;
|
||||
|
||||
public DatabaseMigrationTest() {
|
||||
helper = new MigrationTestHelper(
|
||||
InstrumentationRegistry.getInstrumentation(),
|
||||
AppDatabase.class.getCanonicalName(),
|
||||
new FrameworkSQLiteOpenHelperFactory());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrate1To2() throws Exception {
|
||||
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
|
||||
|
||||
ContentValues users = new ContentValues();
|
||||
for (int i = 1; i < 4; ++i) {
|
||||
users.put("id", i);
|
||||
users.put("username", String.format("test%d", i));
|
||||
users.put("bodyHeight", i * 50);
|
||||
users.put("scaleUnit", 0);
|
||||
users.put("gender", 0);
|
||||
users.put("initialWeight", i * 25);
|
||||
users.put("goalWeight", i * 20);
|
||||
assertNotSame(-1, db.insert("scaleUsers", SQLiteDatabase.CONFLICT_ABORT, users));
|
||||
}
|
||||
|
||||
ContentValues measurement = new ContentValues();
|
||||
for (int i = 2; i < 5; ++i) {
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
measurement.put("userId", i);
|
||||
measurement.put("enabled", j);
|
||||
measurement.put("comment", "a string");
|
||||
for (String type : new String[]{"weight", "fat", "water", "muscle", "lbw", "waist", "hip", "bone"}) {
|
||||
measurement.put(type, i * j);
|
||||
}
|
||||
|
||||
assertNotSame(-1, db.insert("scaleMeasurements", SQLiteDatabase.CONFLICT_ABORT, measurement));
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare for the next version.
|
||||
db.close();
|
||||
|
||||
// Re-open the database with version 2 and provide MIGRATION_1_2 as the migration process.
|
||||
db = helper.runMigrationsAndValidate(TEST_DB, 2, true, AppDatabase.MIGRATION_1_2);
|
||||
|
||||
// MigrationTestHelper automatically verifies the schema changes.
|
||||
|
||||
Cursor cursor = db.query("SELECT * FROM scaleMeasurements ORDER BY id, userId");
|
||||
assertEquals(2 * 2, cursor.getCount());
|
||||
|
||||
cursor.moveToFirst();
|
||||
for (int i = 2; i < 4; ++i) {
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
assertEquals(i, cursor.getInt(cursor.getColumnIndex("userId")));
|
||||
assertEquals(j, cursor.getInt(cursor.getColumnIndex("enabled")));
|
||||
assertEquals("a string", cursor.getString(cursor.getColumnIndex("comment")));
|
||||
for (String type : new String[]{"weight", "fat", "water", "muscle", "lbw", "waist", "hip", "bone"}) {
|
||||
assertEquals((float) i * j, cursor.getFloat(cursor.getColumnIndex(type)));
|
||||
}
|
||||
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(cursor.isAfterLast());
|
||||
}
|
||||
}
|
@@ -16,11 +16,14 @@
|
||||
|
||||
package com.health.openscale.core;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import android.arch.persistence.room.Room;
|
||||
import android.arch.persistence.room.RoomDatabase;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.Log;
|
||||
@@ -81,7 +84,17 @@ public class OpenScale {
|
||||
alarmHandler = new AlarmHandler();
|
||||
btCom = null;
|
||||
fragmentList = new ArrayList<>();
|
||||
appDB = Room.databaseBuilder(context, AppDatabase.class, "openScale.db").allowMainThreadQueries().build();
|
||||
appDB = Room.databaseBuilder(context, AppDatabase.class, "openScale.db")
|
||||
.allowMainThreadQueries()
|
||||
.addCallback(new RoomDatabase.Callback() {
|
||||
@Override
|
||||
public void onOpen(@NonNull SupportSQLiteDatabase db) {
|
||||
super.onOpen(db);
|
||||
db.setForeignKeyConstraintsEnabled(true);
|
||||
}
|
||||
})
|
||||
.addMigrations(AppDatabase.MIGRATION_1_2)
|
||||
.build();
|
||||
measurementDAO = appDB.measurementDAO();
|
||||
userDAO = appDB.userDAO();
|
||||
|
||||
|
@@ -16,18 +16,62 @@
|
||||
|
||||
package com.health.openscale.core.database;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import android.arch.persistence.room.Database;
|
||||
import android.arch.persistence.room.RoomDatabase;
|
||||
import android.arch.persistence.room.TypeConverters;
|
||||
import android.arch.persistence.room.migration.Migration;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.health.openscale.core.datatypes.ScaleMeasurement;
|
||||
import com.health.openscale.core.datatypes.ScaleUser;
|
||||
import com.health.openscale.core.utils.Converters;
|
||||
|
||||
@Database(entities = {ScaleMeasurement.class, ScaleUser.class}, version = 1)
|
||||
@Database(entities = {ScaleMeasurement.class, ScaleUser.class}, version = 2)
|
||||
@TypeConverters({Converters.class})
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
public abstract ScaleMeasurementDAO measurementDAO();
|
||||
public abstract ScaleUserDAO userDAO();
|
||||
|
||||
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
database.beginTransaction();
|
||||
try {
|
||||
// Drop old index on datetime only
|
||||
database.execSQL("DROP INDEX index_scaleMeasurements_datetime");
|
||||
|
||||
// Rename old table
|
||||
database.execSQL("ALTER TABLE scaleMeasurements RENAME TO scaleMeasurementsOld");
|
||||
|
||||
// Create new table with foreign key
|
||||
database.execSQL("CREATE TABLE scaleMeasurements"
|
||||
+ " (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
|
||||
+ " userId INTEGER NOT NULL, enabled INTEGER NOT NULL,"
|
||||
+ " datetime INTEGER, weight REAL NOT NULL, fat REAL NOT NULL,"
|
||||
+ " water REAL NOT NULL, muscle REAL NOT NULL, lbw REAL NOT NULL,"
|
||||
+ " waist REAL NOT NULL, hip REAL NOT NULL, bone REAL NOT NULL,"
|
||||
+ " comment TEXT, FOREIGN KEY(userId) REFERENCES scaleUsers(id)"
|
||||
+ " ON UPDATE NO ACTION ON DELETE CASCADE)");
|
||||
|
||||
// Create new index on datetime + userId
|
||||
database.execSQL("CREATE UNIQUE INDEX index_scaleMeasurements_datetime_userId"
|
||||
+ " ON scaleMeasurements (datetime, userId)");
|
||||
|
||||
// Copy data from the old table, ignoring those with invalid userId (if any)
|
||||
database.execSQL("INSERT INTO scaleMeasurements"
|
||||
+ " SELECT * FROM scaleMeasurementsOld"
|
||||
+ " WHERE userId IN (SELECT id from scaleUsers)");
|
||||
|
||||
// Delete old table
|
||||
database.execSQL("DROP TABLE scaleMeasurementsOld");
|
||||
|
||||
database.setTransactionSuccessful();
|
||||
}
|
||||
finally {
|
||||
database.endTransaction();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -18,6 +18,7 @@ package com.health.openscale.core.datatypes;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.ForeignKey;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
|
||||
@@ -26,7 +27,13 @@ import com.j256.simplecsv.common.CsvColumn;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Entity(tableName = "scaleMeasurements", indices = {@Index(value = {"datetime"}, unique = true)})
|
||||
@Entity(tableName = "scaleMeasurements",
|
||||
indices = {@Index(value = {"datetime", "userId"}, unique = true)},
|
||||
foreignKeys = @ForeignKey(
|
||||
entity = ScaleUser.class,
|
||||
parentColumns = "id",
|
||||
childColumns = "userId",
|
||||
onDelete = ForeignKey.CASCADE))
|
||||
public class ScaleMeasurement implements Cloneable {
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
|
Reference in New Issue
Block a user