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 029b16e66..cfab30ab6 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelperTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelperTest.java @@ -5,6 +5,7 @@ import android.database.Cursor; import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -14,7 +15,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import de.dennisguse.opentracks.content.data.MarkerColumns; @@ -165,6 +168,101 @@ public class CustomSQLiteOpenHelperTest { } } + @Test + public void upgrade_data_to_30() { + // given: a track in version 29 + createVersion23(); + try (SQLiteDatabase db29 = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 29).getWritableDatabase()) { + db29.beginTransaction(); + db29.execSQL("INSERT INTO tracks (_id) VALUES (1)"); + + // Record -> stop + db29.execSQL("INSERT INTO tracks (_id) VALUES (2)"); + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.1, 2.1)"); + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.2, 2.2)"); + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.3, 2.3)"); + + // Record -> pause -> stop + db29.execSQL("INSERT INTO tracks (_id) VALUES (3)"); + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 3.1, 3.1)"); + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 3.2, 3.2)"); + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 0.0, 100)"); //manual PAUSE + + // Record -> segment marker (distance to previous) -> pause -> resume -> stop + db29.execSQL("INSERT INTO tracks (_id) VALUES (4)"); + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.1, 4.1)"); + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.2, 4.2)"); + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 100 * 1E6)"); //SegmentEndMarker; will be deleted + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.3, 4.3)"); + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.4, 4.4)"); + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 100 * 1E6)"); //manual PAUSE + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 200 * 1E6)"); //manual RESUME + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.5, 4.5)"); + db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.6, 4.6)"); + + db29.setTransactionSuccessful(); + db29.endTransaction(); + } + + // when / then + try (SQLiteDatabase db30 = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 30).getWritableDatabase()) { + { + // Track 1 + SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); + queryBuilder.setTables("trackpoints"); + queryBuilder.appendWhere("trackid = 1"); + try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) { + assertEquals(0, cursor.getCount()); + } + } + + { + // Track 2 + SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); + queryBuilder.setTables("trackpoints"); + queryBuilder.appendWhere("trackid = 2"); + try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) { + assertEquals(3, cursor.getCount()); + } + } + + { + // Track 3 + SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); + queryBuilder.setTables("trackpoints"); + queryBuilder.appendWhere("trackid = 3"); + try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) { + assertEquals(3, cursor.getCount()); + } + } + + { + // Track 4 + SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); + queryBuilder.setTables("trackpoints"); + queryBuilder.appendWhere("trackid = 4"); + try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) { + assertEquals(8, cursor.getCount()); + + List types = new ArrayList<>(); + List latitude = new ArrayList<>(); + cursor.moveToFirst(); + do { + types.add(cursor.getInt(cursor.getColumnIndexOrThrow("type"))); + if (!cursor.isNull(cursor.getColumnIndexOrThrow("latitude"))) { + latitude.add(cursor.getDouble(cursor.getColumnIndexOrThrow("latitude"))); + } else { + latitude.add(-1.0); + } + } while (cursor.moveToNext()); + + assertEquals(List.of(0, 0, -1, 0, 1, -2, 0, 0), types); + assertEquals(List.of(4.1, 4.2, 4.3, 4.4, -1.0, -1.0, 4.5, 4.6), latitude); + } + } + } + } + private void createVersion23() { // Manually create database schema with version 23 (base version) SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) { 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 4e859a78f..d2b132224 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java @@ -79,6 +79,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { case 29: upgradeFrom28to29(db); break; + case 30: + upgradeFrom29to30(db); + break; default: throw new RuntimeException("Not implemented: upgrade to " + toVersion); @@ -109,6 +112,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { case 28: downgradeFrom29to28(db); break; + case 29: + downgradeFrom30to29(db); + break; default: throw new RuntimeException("Not implemented: downgrade to " + toVersion); @@ -330,4 +336,62 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { db.setTransactionSuccessful(); db.endTransaction(); } + + /** + * Move TrackPoint type (segment start vs. segment end) into separate column. + */ + private void upgradeFrom29to30(SQLiteDatabase db) { + db.beginTransaction(); + + // TrackPoints + db.execSQL("ALTER TABLE trackpoints ADD COLUMN type TEXT CHECK(type IN (-2, -1, 0, 1))"); + db.execSQL("UPDATE trackpoints SET type = -2, latitude = NULL, longitude = NULL WHERE latitude = 200 * 1E6"); + db.execSQL("UPDATE trackpoints SET type = 1, latitude = NULL, longitude = NULL WHERE latitude = 100 * 1E6"); + db.execSQL("UPDATE trackpoints SET type = 0 WHERE type IS NULL"); + + // PAUSE markers without RESUME were inserted automatically as segment markers if the distance between subsequent trackpoints was too great. + // Only stored data is time (local device time); not meaningful as trackpoints were stored with GPS time. + // 1. Mark there successors as SEGMENT_START_AUTOMATIC + db.execSQL( + "UPDATE trackpoints " + + "SET type = -1 " + + "WHERE type = 0 AND 1 = " + + "(SELECT type FROM trackpoints AS T1 " + + "WHERE trackpoints._id > t1._id " + + "AND trackpoints.trackid = t1.trackid " + + "ORDER BY t1._id DESC " + + "LIMIT 1)"); + // 2. Delete old PAUSE trackpoint + db.execSQL( + "DELETE FROM trackpoints" + + " WHERE type = 1 AND -1 = " + + "(SELECT type FROM trackpoints AS T1 " + + "WHERE trackpoints._id < t1._id " + + "AND trackpoints.trackid = t1.trackid " + + "ORDER BY t1._id ASC " + + "LIMIT 1)"); + + db.setTransactionSuccessful(); + db.endTransaction(); + } + + private void downgradeFrom30to29(SQLiteDatabase db) { + db.beginTransaction(); + + // TrackPoints + //TODO That does not restore deleted trackpoints + db.execSQL("UPDATE trackpoints SET latitude = 200 * 1E6, longitude = NULL WHERE type = -2"); + db.execSQL("UPDATE trackpoints SET latitude = 100 * 1E6, longitude = NULL WHERE type = 1"); + + // TrackPoints; identical to upgradeFrom27to28() + 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)"); + + db.setTransactionSuccessful(); + db.endTransaction(); + } }