From 48c3695b5df54861dba544ec36ac576c7abf388b Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Fri, 30 Oct 2020 21:26:13 +0100 Subject: [PATCH] Database enforces foreign key constraints incl. cascading deletes. (#480) * Database enforces foreign key constraints incl. cascading deletes. Fixes #317. --- .../opentracks/content/SearchEngineTest.java | 63 ++++++++--------- .../CustomContentProviderUtilsTest.java | 38 ++++------- .../provider/CustomSQLiteOpenHelperTest.java | 53 +++++---------- .../io/file/importer/ExportImportTest.java | 7 +- .../opentracks/content/data/Marker.java | 23 ++++--- .../content/data/MarkerColumns.java | 7 +- .../content/data/TrackPointsColumns.java | 8 ++- .../provider/ContentProviderUtils.java | 22 ++---- .../provider/CustomContentProvider.java | 4 ++ .../provider/CustomSQLiteOpenHelper.java | 68 ++++++++++++++++++- 10 files changed, 164 insertions(+), 129 deletions(-) diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/SearchEngineTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/SearchEngineTest.java index 335fe1c20..ff2b4df88 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/SearchEngineTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/SearchEngineTest.java @@ -109,26 +109,21 @@ public class SearchEngineTest { if (hoursAgo >= 0) { location.setTime(NOW - hoursAgo * 1000L * 60L * 60L); } - Marker waypoint = new Marker(location); + Marker waypoint = new Marker(trackId, location); waypoint.setName(title); waypoint.setDescription(description); waypoint.setCategory(category); - waypoint.setTrackId(trackId); Uri uri = providerUtils.insertMarker(waypoint); return new Marker.Id(ContentUris.parseId(uri)); } - private Marker.Id insertWaypoint(String title, String description, String category) { - return insertWaypoint(title, description, category, 0.0, -1, null); + private Marker.Id insertWaypoint(String title, String description, String category, Track.Id trackId) { + return insertWaypoint(title, description, category, 0.0, -1, trackId); } - private Marker.Id insertWaypoint(String title, double distance) { - return insertWaypoint(title, "", "", distance, -1, null); - } - - private Marker.Id insertWaypoint(String title, long hoursAgo) { - return insertWaypoint(title, "", "", 0.0, hoursAgo, null); + private Marker.Id insertWaypoint(String title, double distance, Track.Id trackId) { + return insertWaypoint(title, "", "", distance, -1, trackId); } private Marker.Id insertWaypoint(String title, long hoursAgo, Track.Id trackId) { @@ -170,13 +165,14 @@ public class SearchEngineTest { // - one which will match in title and category // - one which will match in title and description // - one which will match in all fields - insertWaypoint("bb", "cc", "dd"); - Marker.Id descriptionMatchId = insertWaypoint("bb", "aa", "cc"); - Marker.Id categoryMatchId = insertWaypoint("bb", "cc", "aa"); - Marker.Id titleMatchId = insertWaypoint("aa", "bb", "cc"); - Marker.Id titleCategoryMatchId = insertWaypoint("aa", "bb", "ca"); - Marker.Id titleDescriptionMatchId = insertWaypoint("aa", "ba", "cc"); - Marker.Id allMatchId = insertWaypoint("aa", "ba", "ca"); + Track.Id trackId = insertTrack("", "", ""); + insertWaypoint("bb", "cc", "dd", trackId); + Marker.Id descriptionMatchId = insertWaypoint("bb", "aa", "cc", trackId); + Marker.Id categoryMatchId = insertWaypoint("bb", "cc", "aa", trackId); + Marker.Id titleMatchId = insertWaypoint("aa", "bb", "cc", trackId); + Marker.Id titleCategoryMatchId = insertWaypoint("aa", "bb", "ca", trackId); + Marker.Id titleDescriptionMatchId = insertWaypoint("aa", "ba", "cc", trackId); + Marker.Id allMatchId = insertWaypoint("aa", "ba", "ca", trackId); SearchQuery query = new SearchQuery("a", null, null, NOW); ArrayList results = new ArrayList<>(engine.search(query)); @@ -193,11 +189,11 @@ public class SearchEngineTest { // - one waypoint which will match by title // - one track which won't match // - one track which will match by title - Marker.Id descriptionWaypointId = insertWaypoint("bb", "aa", "cc"); - insertWaypoint("bb", "cc", "dd"); - Marker.Id titleWaypointId = insertWaypoint("aa", "bb", "cc"); - insertTrack("bb", "cc", "dd"); Track.Id trackId = insertTrack("aa", "bb", "cc"); + Marker.Id descriptionWaypointId = insertWaypoint("bb", "aa", "cc", trackId); + insertWaypoint("bb", "cc", "dd", trackId); + Marker.Id titleWaypointId = insertWaypoint("aa", "bb", "cc", trackId); + insertTrack("bb", "cc", "dd"); SearchQuery query = new SearchQuery("a", null, null, NOW); ArrayList results = new ArrayList<>(engine.search(query)); @@ -212,9 +208,10 @@ public class SearchEngineTest { @Test public void testSearchWaypointDistance() { // All results match text, but they're at difference distances from the user. - Marker.Id farFarAwayId = insertWaypoint("aa", 0.3); - Marker.Id nearId = insertWaypoint("ab", 0.1); - Marker.Id farId = insertWaypoint("ac", 0.2); + Track.Id trackId = insertTrack("", "", ""); + Marker.Id farFarAwayId = insertWaypoint("aa", 0.3, trackId); + Marker.Id nearId = insertWaypoint("ab", 0.1, trackId); + Marker.Id farId = insertWaypoint("ac", 0.2, trackId); SearchQuery query = new SearchQuery("a", HERE, null, NOW); ArrayList results = new ArrayList<>(engine.search(query)); @@ -239,9 +236,10 @@ public class SearchEngineTest { @Test public void testSearchWaypointRecent() { // All results match text, but they're were recorded at different times. - Marker.Id oldestId = insertWaypoint("aa", 2); - Marker.Id recentId = insertWaypoint("ab", 0); - Marker.Id oldId = insertWaypoint("ac", 1); + Track.Id trackId = insertTrack("", "", ""); + Marker.Id oldestId = insertWaypoint("aa", 2, trackId); + Marker.Id recentId = insertWaypoint("ab", 0, trackId); + Marker.Id oldId = insertWaypoint("ac", 1, trackId); SearchQuery query = new SearchQuery("a", null, null, NOW); ArrayList results = new ArrayList<>(engine.search(query)); @@ -266,10 +264,13 @@ public class SearchEngineTest { @Test public void testSearchCurrentTrackWaypoint() { // All results match text, but one of them is in the current track. - Marker.Id otherId = insertWaypoint("aa", 1, new Track.Id(456)); - Marker.Id currentId = insertWaypoint("ab", 1, new Track.Id(123)); + Track.Id trackId1 = insertTrack("", "", ""); + Track.Id trackId2 = insertTrack("", "", ""); - SearchQuery query = new SearchQuery("a", null, new Track.Id(123), NOW); + Marker.Id otherId = insertWaypoint("aa", 1, trackId2); + Marker.Id currentId = insertWaypoint("ab", 1, trackId1); + + SearchQuery query = new SearchQuery("a", null, trackId1, NOW); ArrayList results = new ArrayList<>(engine.search(query)); // Waypoint in current track should be promoted. @@ -316,7 +317,7 @@ public class SearchEngineTest { ScoredResult result = results.get(i); assertNotNull(errMsg, result.marker); assertNull(errMsg, result.track); - assertEquals(errMsg, waypointIds[i], result.marker.getId()); + assertEquals(errMsg, waypointIds[i].getId(), result.marker.getId().getId()); } } } diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java index 339c13e9f..792cc47bf 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java @@ -212,7 +212,7 @@ public class CustomContentProviderUtilsTest { Track.Id trackId = new Track.Id(System.currentTimeMillis()); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); - Marker waypoint = new Marker(contentProviderUtils.getLastValidTrackPoint(trackId)); + Marker waypoint = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId)); contentProviderUtils.insertMarker(waypoint); ContentResolver contentResolver = context.getContentResolver(); @@ -287,8 +287,7 @@ public class CustomContentProviderUtilsTest { TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId2, 10); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId3, 10); - Marker waypoint = new Marker(contentProviderUtils.getLastValidTrackPoint(trackId2)); - waypoint.setTrackId(trackId1); + Marker waypoint = new Marker(trackId1, contentProviderUtils.getLastValidTrackPoint(trackId2)); contentProviderUtils.insertMarker(waypoint); ContentResolver contentResolver = context.getContentResolver(); @@ -463,7 +462,7 @@ public class CustomContentProviderUtilsTest { track.first.setTrackStatistics(statistics); contentProviderUtils.insertTrack(track.first); - Marker waypoint = new Marker(track.second[0]); + Marker waypoint = new Marker(trackId, track.second[0]); waypoint.setDescription(TEST_DESC); contentProviderUtils.insertMarker(waypoint); @@ -522,9 +521,8 @@ public class CustomContentProviderUtilsTest { TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); // Insert at first. - Marker waypoint1 = new Marker(contentProviderUtils.getLastValidTrackPoint(trackId)); + Marker waypoint1 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId)); waypoint1.setDescription(TEST_DESC); - waypoint1.setTrackId(trackId); contentProviderUtils.insertMarker(waypoint1); // Check insert was done. @@ -600,14 +598,12 @@ public class CustomContentProviderUtilsTest { // TestDataUtil.insertTrackWithLocations(contentProviderUtils, track); // Insert at first. - Marker waypoint1 = new Marker(contentProviderUtils.getLastValidTrackPoint(trackId)); + Marker waypoint1 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId)); waypoint1.setDescription(MOCK_DESC); - waypoint1.setTrackId(trackId); Marker.Id waypoint1Id = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint1))); - Marker waypoint2 = new Marker(contentProviderUtils.getLastValidTrackPoint(trackId)); + Marker waypoint2 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId)); waypoint2.setDescription(MOCK_DESC); - waypoint2.setTrackId(trackId); Marker.Id waypoint2Id = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint2))); // Delete @@ -626,14 +622,10 @@ public class CustomContentProviderUtilsTest { Track.Id trackId = new Track.Id(System.currentTimeMillis()); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); - Marker waypoint1 = new Marker(contentProviderUtils.getLastValidTrackPoint(trackId)); - waypoint1.setTrackId(trackId); - Marker waypoint2 = new Marker(contentProviderUtils.getLastValidTrackPoint(trackId)); - waypoint2.setTrackId(trackId); - Marker waypoint3 = new Marker(contentProviderUtils.getLastValidTrackPoint(trackId)); - waypoint3.setTrackId(trackId); - Marker waypoint4 = new Marker(contentProviderUtils.getLastValidTrackPoint(trackId)); - waypoint4.setTrackId(trackId); + Marker waypoint1 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId)); + Marker waypoint2 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId)); + Marker waypoint3 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId)); + Marker waypoint4 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId)); contentProviderUtils.insertMarker(waypoint1); contentProviderUtils.insertMarker(waypoint2); contentProviderUtils.insertMarker(waypoint3); @@ -651,9 +643,8 @@ public class CustomContentProviderUtilsTest { Track.Id trackId = new Track.Id(System.currentTimeMillis()); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); - Marker waypoint = new Marker(contentProviderUtils.getLastValidTrackPoint(trackId)); + Marker waypoint = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId)); waypoint.setDescription(TEST_DESC); - waypoint.setTrackId(trackId); Marker.Id waypointId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint))); assertEquals(TEST_DESC, contentProviderUtils.getMarker(waypointId).getDescription()); @@ -668,9 +659,8 @@ public class CustomContentProviderUtilsTest { TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); // Insert at first. - Marker waypoint = new Marker(contentProviderUtils.getLastValidTrackPoint(trackId)); + Marker waypoint = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId)); waypoint.setDescription(TEST_DESC); - waypoint.setTrackId(trackId); Marker.Id waypointId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint))); // Update @@ -695,7 +685,6 @@ public class CustomContentProviderUtilsTest { TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId); Marker waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation()); waypoint.setDescription(TEST_DESC); - waypoint.setTrackId(trackId); Marker.Id waypointId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint))); File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId()); @@ -731,7 +720,6 @@ public class CustomContentProviderUtilsTest { TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId); Marker waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation()); waypoint.setDescription(TEST_DESC); - waypoint.setTrackId(trackId); Marker.Id waypointId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint))); File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId()); @@ -766,10 +754,8 @@ public class CustomContentProviderUtilsTest { TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId); Marker waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation()); waypoint.setDescription(TEST_DESC); - waypoint.setTrackId(trackId); Marker otherWaypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation()); otherWaypoint.setDescription(TEST_DESC); - otherWaypoint.setTrackId(trackId); Marker.Id waypointId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint))); contentProviderUtils.insertMarker(otherWaypoint); diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelperTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelperTest.java index f776cae7b..029b16e66 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelperTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelperTest.java @@ -6,7 +6,6 @@ import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import androidx.annotation.VisibleForTesting; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -38,39 +37,21 @@ public class CustomSQLiteOpenHelperTest { private final Context context = ApplicationProvider.getApplicationContext(); /** - * Get the SQL create statements for all tables (ordered by name). + * Get the SQL create statements for all SQLite elements of type (ordered by name). * - * @return Map(TableName, SQL) + * @param type index, table + * @return Map(name, SQL) */ - @VisibleForTesting - public static Map getTableSQL(SQLiteDatabase db) { - HashMap tableSQL = new HashMap<>(); - try (Cursor cursor = db.query("sqlite_master", new String[]{"name", "SQL"}, "name IN ('" + TracksColumns.TABLE_NAME + "', '" + TrackPointsColumns.TABLE_NAME + "', '" + MarkerColumns.TABLE_NAME + "')", null, null, null, "name")) { + public static Map getSQL(SQLiteDatabase db, String type) { + HashMap sqlMap = new HashMap<>(); + try (Cursor cursor = db.query("sqlite_master", new String[]{"name", "SQL"}, "type=?", new String[]{type}, null, null, "name")) { if (cursor != null) { while (cursor.moveToNext()) { - tableSQL.put(cursor.getString(0), cursor.getString(1)); + sqlMap.put(cursor.getString(0), cursor.getString(1)); } } } - return tableSQL; - } - - /** - * Get the SQL create statements for all indices (ordered by name). - * - * @return Map(tableName, SQL) - */ - @VisibleForTesting - public static Map getIndexSQL(SQLiteDatabase db) { - HashMap indexSQL = new HashMap<>(); - try (Cursor cursor = db.rawQuery("SELECT tbl_name, SQL FROM sqlite_master WHERE type = 'index'", null)) { - if (cursor != null) { - while (cursor.moveToNext()) { - indexSQL.put(cursor.getString(0), cursor.getString(1)); - } - } - } - return indexSQL; + return sqlMap; } /** @@ -109,7 +90,7 @@ public class CustomSQLiteOpenHelperTest { assertTrue(hasSqlCreate(db, MarkerColumns.CREATE_TABLE)); assertTrue(hasSqlCreate(db, MarkerColumns.CREATE_TABLE_INDEX)); } catch (Exception e) { - fail(); + fail("Database could not be created: " + e); } } @@ -121,8 +102,8 @@ public class CustomSQLiteOpenHelperTest { Map tableByUpgrade; Map indicesByUpgrade; try (SQLiteDatabase dbUpgraded = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) { - tableByUpgrade = getTableSQL(dbUpgraded); - indicesByUpgrade = getIndexSQL(dbUpgraded); + tableByUpgrade = getSQL(dbUpgraded, "table"); + indicesByUpgrade = getSQL(dbUpgraded, "index"); } context.deleteDatabase(DATABASE_NAME); @@ -130,12 +111,14 @@ public class CustomSQLiteOpenHelperTest { Map tablesByCreate; Map indicesByCreate; try (SQLiteDatabase dbCreated = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) { - tablesByCreate = getTableSQL(dbCreated); - indicesByCreate = getIndexSQL(dbCreated); + tablesByCreate = getSQL(dbCreated, "table"); + indicesByCreate = getSQL(dbCreated, "index"); } + // then - verify table structure - assertEquals(3, tableByUpgrade.size()); + int tableCount = 3 + 2; //Three with data tables + two SQLite + assertEquals(tableCount, tableByUpgrade.size()); assertEquals(tableByUpgrade.size(), tablesByCreate.size()); assertEquals(tablesByCreate.get(TracksColumns.TABLE_NAME), tableByUpgrade.get(TracksColumns.TABLE_NAME)); @@ -158,8 +141,8 @@ public class CustomSQLiteOpenHelperTest { Map tablesByDowngrade; Map indicesByDowngrade; try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 23).getReadableDatabase()) { - tablesByDowngrade = getTableSQL(db); - indicesByDowngrade = getIndexSQL(db); + tablesByDowngrade = getSQL(db, "table"); + indicesByDowngrade = getSQL(db, "index"); } // then - verify table structure diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java index 1c12b8fc0..c89822828 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java @@ -72,13 +72,12 @@ public class ExportImportTest { trackPoints.addAll(Arrays.asList(track.second)); for (int i = 0; i < 3; i++) { - Marker marker = new Marker(track.second[i].getLocation()); + Marker marker = new Marker(trackId, track.second[i].getLocation()); marker.setName("the marker " + i); marker.setDescription("the marker description " + i); marker.setCategory("the marker category" + i); marker.setIcon("the marker icon" + i); marker.setPhotoUrl("the photo url" + i); - marker.setTrackId(trackId); contentProviderUtils.insertMarker(marker); markers.add(marker); @@ -90,7 +89,9 @@ public class ExportImportTest { @After public void tearDown() { contentProviderUtils.deleteTrack(context, trackId); - contentProviderUtils.deleteTrack(context, importTrackId); + if (importTrackId != null) { + contentProviderUtils.deleteTrack(context, importTrackId); + } } @LargeTest diff --git a/src/main/java/de/dennisguse/opentracks/content/data/Marker.java b/src/main/java/de/dennisguse/opentracks/content/data/Marker.java index a056ca09e..62356650c 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/Marker.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/Marker.java @@ -40,20 +40,28 @@ public final class Marker { private String description = ""; private String category = ""; private String icon = ""; - private Track.Id trackId; + private final Track.Id trackId; + //TODO It is the distance from the track starting point; rename to something more meaningful private double length = 0.0; private long duration = 0; - private Location location; + private final Location location; @Deprecated //TODO Make an URI instead of String private String photoUrl = ""; @VisibleForTesting - public Marker(@NonNull TrackPoint trackPoint) { - this.location = trackPoint.getLocation(); + public Marker(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) { + this(trackId, trackPoint.getLocation()); } + @Deprecated + //TODO Used by AbstractFileImporter to create an intermediate marker before saving a new one into the database. public Marker(@NonNull Location location) { + this(null, location); + } + + public Marker(@NonNull Track.Id trackId, @NonNull Location location) { this.location = location; + this.trackId = trackId; } public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, double length, long duration, @NonNull Location location, String photoUrl) { @@ -112,14 +120,11 @@ public final class Marker { this.icon = icon; } - public Track.Id getTrackId() { + public @NonNull + Track.Id getTrackId() { return trackId; } - public void setTrackId(Track.Id trackId) { - this.trackId = trackId; - } - public double getLength() { return length; } diff --git a/src/main/java/de/dennisguse/opentracks/content/data/MarkerColumns.java b/src/main/java/de/dennisguse/opentracks/content/data/MarkerColumns.java index a410d0797..69bf939ce 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/MarkerColumns.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/MarkerColumns.java @@ -28,7 +28,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils; */ public interface MarkerColumns extends BaseColumns { - String TABLE_NAME = "waypoints"; + String TABLE_NAME = "markers"; Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME); Uri CONTENT_URI_BY_TRACKID = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/trackid"); String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint"; @@ -60,7 +60,7 @@ public interface MarkerColumns extends BaseColumns { + DESCRIPTION + " TEXT, " + CATEGORY + " TEXT, " + ICON + " TEXT, " - + TRACKID + " INTEGER, " + + TRACKID + " INTEGER NOT NULL, " + LENGTH + " FLOAT, " + DURATION + " INTEGER, " + LONGITUDE + " INTEGER, " @@ -69,7 +69,8 @@ public interface MarkerColumns extends BaseColumns { + ALTITUDE + " FLOAT, " + ACCURACY + " FLOAT, " + BEARING + " FLOAT, " - + PHOTOURL + " TEXT" + + PHOTOURL + " TEXT, " + + "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE" + ")"; String CREATE_TABLE_INDEX = "CREATE INDEX " + TABLE_NAME + "_" + TRACKID + "_index ON " + TABLE_NAME + "(" + TRACKID + ")"; diff --git a/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java b/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java index 59fa38806..518d352f3 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java @@ -40,7 +40,9 @@ public interface TrackPointsColumns extends BaseColumns { String LONGITUDE = "longitude"; String LATITUDE = "latitude"; + @Deprecated double PAUSE_LATITUDE = 100.0; + @Deprecated double RESUME_LATITUDE = 200.0; String TIME = "time"; @@ -55,7 +57,7 @@ public interface TrackPointsColumns extends BaseColumns { String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + TRACKID + " INTEGER, " + + TRACKID + " INTEGER NOT NULL, " + LONGITUDE + " INTEGER, " + LATITUDE + " INTEGER, " + TIME + " INTEGER, " @@ -66,7 +68,9 @@ public interface TrackPointsColumns extends BaseColumns { + SENSOR_HEARTRATE + " FLOAT, " + SENSOR_CADENCE + " FLOAT, " + SENSOR_POWER + " FLOAT, " - + ELEVATION_GAIN + " FLOAT)"; + + ELEVATION_GAIN + " FLOAT, " + + "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE" + + ")"; String CREATE_TABLE_INDEX = "CREATE INDEX " + TABLE_NAME + "_" + TRACKID + "_index ON " + TABLE_NAME + "(" + TRACKID + ")"; } diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java index 33121e00b..2e9aad720 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java @@ -179,17 +179,7 @@ public class ContentProviderUtils { FileUtils.deleteDirectoryRecurse(dir); } - /** - * Deletes a track. - * - * @param trackId the track id - */ - public void deleteTrack(Context context, Track.Id trackId) { - if (trackId == null) { - return; - } - deleteTrackPointsAndMarkers(trackId); - + public void deleteTrack(Context context, @NonNull Track.Id trackId) { // Delete track folder resources. FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId)); @@ -363,7 +353,8 @@ public class ContentProviderUtils { location.setBearing(cursor.getFloat(bearingIndex)); } - Marker marker = new Marker(location); + Track.Id trackId = new Track.Id(cursor.getLong(trackIdIndex)); + Marker marker = new Marker(trackId, location); if (!cursor.isNull(idIndex)) { marker.setId(new Marker.Id(cursor.getLong(idIndex))); @@ -380,9 +371,6 @@ public class ContentProviderUtils { if (!cursor.isNull(iconIndex)) { marker.setIcon(cursor.getString(iconIndex)); } - if (!cursor.isNull(trackIdIndex)) { - marker.setTrackId(new Track.Id(cursor.getLong(trackIdIndex))); - } if (!cursor.isNull(lengthIndex)) { marker.setLength(cursor.getFloat(lengthIndex)); } @@ -561,9 +549,7 @@ public class ContentProviderUtils { values.put(MarkerColumns.DESCRIPTION, marker.getDescription()); values.put(MarkerColumns.CATEGORY, marker.getCategory()); values.put(MarkerColumns.ICON, marker.getIcon()); - if (marker.getTrackId() != null) { - values.put(MarkerColumns.TRACKID, marker.getTrackId().getId()); - } + values.put(MarkerColumns.TRACKID, marker.getTrackId().getId()); values.put(MarkerColumns.LENGTH, marker.getLength()); values.put(MarkerColumns.DURATION, marker.getDuration()); diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/CustomContentProvider.java b/src/main/java/de/dennisguse/opentracks/content/provider/CustomContentProvider.java index 8babebc5b..6df820754 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/CustomContentProvider.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/CustomContentProvider.java @@ -39,6 +39,8 @@ import de.dennisguse.opentracks.content.data.TracksColumns; /** * A {@link ContentProvider} that handles access to track points, tracks, and markers tables. + *

+ * Data consistency is enforced using Foreign Key Constraints within the database incl. cascading deletes. * * @author Leif Hendrik Wilden */ @@ -82,6 +84,8 @@ public class CustomContentProvider extends ContentProvider { CustomSQLiteOpenHelper databaseHelper = new CustomSQLiteOpenHelper(context); try { db = databaseHelper.getWritableDatabase(); + // Necessary to enable cascade deletion from Track to TrackPoints and Markers + db.setForeignKeyConstraintsEnabled(true); } catch (SQLiteException e) { Log.e(TAG, "Unable to open database for writing.", e); } diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java b/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java index ff41e07d5..a6572f4cd 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java @@ -25,7 +25,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName(); - private static final int DATABASE_VERSION = 27; + private static final int DATABASE_VERSION = 28; @VisibleForTesting public static final String DATABASE_NAME = "database.db"; @@ -73,7 +73,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { case 27: upgradeFrom26to27(db); break; - + case 28: + upgradeFrom27to28(db); + break; default: throw new RuntimeException("Not implemented: upgrade to " + toVersion); @@ -98,6 +100,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { case 26: downgradeFrom27to26(db); break; + case 27: + downgradeFrom28to27(db); + break; default: throw new RuntimeException("Not implemented: downgrade to " + toVersion); @@ -142,6 +147,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { db.endTransaction(); } + /** + * Add indeces for foreign key trackId + */ private void upgradeFrom24to25(SQLiteDatabase db) { db.beginTransaction(); @@ -162,6 +170,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { db.endTransaction(); } + /** + * Add track UUID to prevent re-import of existing tracks + */ private void upgradeFrom25to26(SQLiteDatabase db) { db.beginTransaction(); @@ -198,6 +209,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { db.endTransaction(); } + /** + * Add elevation gain + */ private void upgradeFrom26to27(SQLiteDatabase db) { db.beginTransaction(); @@ -220,4 +234,54 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { db.setTransactionSuccessful(); db.endTransaction(); } + + /** + * Add foreign key constraints on trackId + */ + private void upgradeFrom27to28(SQLiteDatabase db) { + db.beginTransaction(); + + // TrackPoints + db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old"); + db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT, elevation_gain FLOAT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)"); + db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, sensor_cadence, sensor_power, elevation_gain FROM trackpoints_old"); + db.execSQL("DROP TABLE trackpoints_old"); + + db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)"); + + // Markers + db.execSQL("ALTER TABLE waypoints RENAME TO markers_old"); + db.execSQL("CREATE TABLE markers (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER NOT NULL, length FLOAT, duration INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, bearing FLOAT, photoUrl TEXT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)"); + + db.execSQL("INSERT INTO markers SELECT _id, name, description, category, icon, trackid, length, duration, longitude, latitude, time, elevation, accuracy, bearing, photoUrl FROM markers_old"); + db.execSQL("DROP TABLE markers_old"); + + db.execSQL("CREATE INDEX markers_trackid_index ON markers(trackid)"); + + db.setTransactionSuccessful(); + db.endTransaction(); + } + + private void downgradeFrom28to27(SQLiteDatabase db) { + db.beginTransaction(); + + // TrackPoints + db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old"); + db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT, elevation_gain FLOAT)"); + db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, sensor_cadence, sensor_power, elevation_gain FROM trackpoints_old"); + db.execSQL("DROP TABLE trackpoints_old"); + + db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)"); + + // Markers + db.execSQL("CREATE TABLE waypoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER, length FLOAT, duration INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, bearing FLOAT, photoUrl TEXT)"); + + db.execSQL("INSERT INTO waypoints SELECT _id, name, description, category, icon, trackid, length, duration, longitude, latitude, time, elevation, accuracy, bearing, photoUrl FROM markers"); + db.execSQL("DROP TABLE markers"); + + db.execSQL("CREATE INDEX waypoints_trackid_index ON waypoints(trackid)"); + + db.setTransactionSuccessful(); + db.endTransaction(); + } }