Database enforces foreign key constraints incl. cascading deletes. (#480)

* Database enforces foreign key constraints incl. cascading deletes.
Fixes #317.
This commit is contained in:
Dennis Guse
2020-10-30 21:26:13 +01:00
committed by GitHub
parent 2b280f050c
commit 48c3695b5d
10 changed files with 164 additions and 129 deletions
@@ -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;
}
@@ -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 + ")";
@@ -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 + ")";
}
@@ -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());
@@ -39,6 +39,8 @@ import de.dennisguse.opentracks.content.data.TracksColumns;
/**
* A {@link ContentProvider} that handles access to track points, tracks, and markers tables.
* <p>
* 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);
}
@@ -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();
}
}