Skip to content

Commit

Permalink
Migrate from "id" to "guid".
Browse files Browse the repository at this point in the history
!!! ATTENTION !!! This change is NOT compatible to UPDATE exiting apps!

+ As of this commit the "id" XML attribute is no longer used as the unique
  token because its value cannot be guaranteed to be unique by the provider
  of the schedule XML. See voc/schedule#63.
+ Instead the "guid" XML attribute is used which guarantees unique values.
+ To keep this migration to a minimum only the value of the Session#sessionId
  field is changed. The value of the new primary key (auto-incrementing,
  unique through the "guid" column) is written into the "sessionId" field
  when a Session is queried from the database. See SessionsDatabaseRepository.
+ By this the value of the "id" XML attribute becomes unused hence the
  deprecation of the SESSION_ID database column.
  • Loading branch information
johnjohndoe committed Jul 30, 2021
1 parent 8ae1cfd commit 1c895b3
Show file tree
Hide file tree
Showing 15 changed files with 68 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ fun Session.toHighlightDatabaseModel() = HighlightDatabaseModel(

fun Session.toSessionDatabaseModel() = SessionDatabaseModel(
sessionId = sessionId,
guid = guid,
abstractt = abstractt,
date = date,
dateUTC = dateUTC,
Expand Down Expand Up @@ -75,6 +76,7 @@ fun Session.toSessionDatabaseModel() = SessionDatabaseModel(

fun SessionDatabaseModel.toSessionAppModel(): Session {
val session = Session(sessionId)
session.guid = guid

session.abstractt = abstractt
session.date = date
Expand Down Expand Up @@ -119,6 +121,7 @@ fun SessionDatabaseModel.toSessionAppModel(): Session {

fun SessionNetworkModel.toSessionAppModel(): Session {
val session = Session(sessionId)
session.guid = guid

session.abstractt = abstractt
session.date = date
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
*/
public class Session {

public String sessionId;
public String guid = "";

public String title;
public String subtitle;
public String url;
Expand All @@ -46,7 +49,6 @@ public class Session {

public String speakers;
public String track;
public String sessionId;
public String type;
public String lang;
public String slug;
Expand Down Expand Up @@ -80,6 +82,7 @@ public class Session {
private static final boolean RECORDING_OPTOUT_OFF = false;

public Session(String sessionId) {
guid = "";
title = "";
subtitle = "";
day = 0;
Expand Down Expand Up @@ -119,6 +122,7 @@ public Session(String sessionId) {
}

public Session(@NonNull Session session) {
this.guid = session.guid;
this.title = session.title;
this.subtitle = session.subtitle;
this.url = session.url;
Expand Down Expand Up @@ -208,6 +212,7 @@ public boolean equals(Object o) {
if (!ObjectsCompat.equals(date, session.date)) return false;
if (!ObjectsCompat.equals(lang, session.lang)) return false;
if (!sessionId.equals(session.sessionId)) return false;
if (!guid.equals(session.guid)) return false;
if (!ObjectsCompat.equals(recordingLicense, session.recordingLicense)) return false;
if (!ObjectsCompat.equals(room, session.room)) return false;
if (!ObjectsCompat.equals(speakers, session.speakers)) return false;
Expand All @@ -232,6 +237,7 @@ public int hashCode() {
result = 31 * result + ObjectsCompat.hashCode(speakers);
result = 31 * result + ObjectsCompat.hashCode(track);
result = 31 * result + sessionId.hashCode();
result = 31 * result + guid.hashCode();
result = 31 * result + ObjectsCompat.hashCode(type);
result = 31 * result + ObjectsCompat.hashCode(lang);
result = 31 * result + ObjectsCompat.hashCode(date);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -398,9 +398,9 @@ object AppRepository {

private fun updateSessions(toBeUpdatedSessions: List<Session>, toBeDeletedSessions: List<Session> = emptyList()) {
val toBeUpdatedSessionsDatabaseModel = toBeUpdatedSessions.toSessionsDatabaseModel()
val toBeUpdated = toBeUpdatedSessionsDatabaseModel.map { it.sessionId to it.toContentValues() }
val toBeDeleted = toBeDeletedSessions.map { it.sessionId }
sessionsDatabaseRepository.updateSessions(toBeUpdated, toBeDeleted)
val toBeUpdated = toBeUpdatedSessionsDatabaseModel.map { it.guid to it.toContentValues() }
val toBeDeleted = toBeDeletedSessions.map { it.guid }
sessionsDatabaseRepository.upsertSessions(toBeUpdated, toBeDeleted)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ data class ScheduleChanges private constructor(
var sessionIndex = 0
while (sessionIndex < newSessions.size) {
val newSession = newSessions[sessionIndex]
val oldSession = oldNotCanceledSessions.singleOrNull { oldNotCanceledSession -> newSession.sessionId == oldNotCanceledSession.sessionId }
val oldSession = oldNotCanceledSessions.singleOrNull { oldNotCanceledSession -> newSession.guid == oldNotCanceledSession.guid }
if (oldSession == null) {
sessionsWithChangeFlags += SessionAppModel(newSession).apply { changedIsNew = true }
foundChanges = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class SessionExtensionsTest {
fun sessionDatabaseModel_toSessionAppModel_toSessionDatabaseModel() {
val session = SessionDatabaseModel(
sessionId = "7331",
guid = "11111111-1111-1111-1111-111111111111",
abstractt = "Lorem ipsum",
dayIndex = 3,
date = "2015-08-13",
Expand Down Expand Up @@ -63,6 +64,7 @@ class SessionExtensionsTest {
fun sessionNetworkModel_toSessionAppModel() {
val sessionNetworkModel = SessionNetworkModel(
sessionId = "7331",
guid = "11111111-1111-1111-1111-111111111111",
abstractt = "Lorem ipsum",
dayIndex = 3,
date = "2015-08-13",
Expand Down Expand Up @@ -102,6 +104,7 @@ class SessionExtensionsTest {
changedTrack = true
)
val sessionAppModel = SessionAppModel("7331").apply {
guid = "11111111-1111-1111-1111-111111111111"
abstractt = "Lorem ipsum"
day = 3
date = "2015-08-13"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class SessionTest {
companion object {

fun createSession() = Session("s1").apply {
guid = "11111111-1111-1111-1111-111111111111"
title = "Lorem ipsum"
subtitle = "Gravida arcu ac tortor"
day = 3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,11 @@ class ScheduleChangesTest {
assertThat(scheduleChanges.foundChanges).isTrue()
}

private fun createSession(sessionId: String = "1", block: Session.() -> Unit = {}) = Session(sessionId).apply(block)
private fun createSession(sessionId: String = "1", block: Session.() -> Unit = {}): Session {
return Session(sessionId).apply {
guid = sessionId // shortcut for testing purpose only!
block()
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class SessionExtensionsTest {
fun toContentValues() {
val session = Session(
sessionId = "7331",
guid = "11111111-1111-1111-1111-111111111111",
abstractt = "Lorem ipsum",
dayIndex = 3,
date = "2015-08-13",
Expand Down Expand Up @@ -53,6 +54,7 @@ class SessionExtensionsTest {
)
val values = session.toContentValues()
assertThat(values.getAsInteger(SESSION_ID)).isEqualTo(7331)
assertThat(values.getAsString(GUID)).isEqualTo("11111111-1111-1111-1111-111111111111")
assertThat(values.getAsString(ABSTRACT)).isEqualTo("Lorem ipsum")
assertThat(values.getAsInteger(DAY)).isEqualTo(3)
assertThat(values.getAsString(DATE)).isEqualTo("2015-08-13")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ interface SessionsTable {

interface Columns extends BaseColumns {

@Deprecated // Value is unused. Query primary key _ID column instead.
/* 00 */ String SESSION_ID = "event_id"; // Keep column name to avoid database migration.
/* 01 */ String TITLE = "title";
/* 02 */ String SUBTITLE = "subtitle";
Expand Down Expand Up @@ -124,6 +125,7 @@ interface Columns extends BaseColumns {
/* 32 */ String SLUG = "slug";
/* 33 */ String URL = "url";
/* 34 */ String TIME_ZONE_OFFSET = "time_zone_offset";
/* 35 */ String GUID = "guid";
}

interface Defaults {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.Se
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.DAY
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.DESCR
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.DURATION
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.GUID
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.LANG
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.LINKS
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.REC_LICENSE
Expand All @@ -44,6 +45,7 @@ import info.metadude.android.eventfahrplan.database.models.Session

fun Session.toContentValues() = contentValuesOf(
SESSION_ID to sessionId,
GUID to guid,
ABSTRACT to abstractt,
DAY to dayIndex,
DATE to date,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package info.metadude.android.eventfahrplan.database.models
data class Session(

val sessionId: String,
val guid: String,
val abstractt: String = "",
val dayIndex: Int = 0,
val date: String = "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.Se
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.DAY
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.DESCR
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.DURATION
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.GUID
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.LANG
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.LINKS
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.REC_LICENSE
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.REC_OPTOUT
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.REL_START
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.ROOM
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.ROOM_IDX
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.SESSION_ID
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.SLUG
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.SPEAKERS
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.START
Expand All @@ -43,6 +43,7 @@ import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.Se
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.TRACK
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.TYPE
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.URL
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns._ID
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Values.REC_OPT_OUT_OFF
import info.metadude.android.eventfahrplan.database.extensions.delete
import info.metadude.android.eventfahrplan.database.extensions.getInt
Expand Down Expand Up @@ -88,36 +89,39 @@ class SessionsDatabaseRepository(


/**
* Updates or inserts sessions based on the given [contentValuesBySessionId].
* Removes all sessions identified by their [session IDs][toBeDeletedSessionIds].
* Updates or inserts sessions based on the given [contentValuesByGuid].
* Removes all sessions identified by their [session GUIDs][toBeDeletedSessionGuids].
*/
fun updateSessions(
contentValuesBySessionId: List<Pair</* sessionId */ String, ContentValues>>,
toBeDeletedSessionIds: List</* sessionId */ String>
fun upsertSessions(
contentValuesByGuid: List<Pair</* guid */ String, ContentValues>>,
toBeDeletedSessionGuids: List</* sessionGuid */ String>
) = with(sqLiteOpenHelper) {
writableDatabase.transaction {
contentValuesBySessionId.forEach { (sessionId, contentValues) ->
upsertSession(sessionId, contentValues)
contentValuesByGuid.forEach { (guid, contentValues) ->
upsertSession(guid, contentValues)
}
toBeDeletedSessionIds.forEach { toBeDeletedSessionId ->
deleteSession(toBeDeletedSessionId)
toBeDeletedSessionGuids.forEach { toBeDeletedSessionGuid ->
deleteSession(toBeDeletedSessionGuid)
}
}
}

/**
* Updates a session with the given [contentValues]. A row is matched by its [sessionId].
* Updates a session with the given [contentValues]. A row is matched by its [guid].
* If no row was affected by the update operation then an insert operation is performed
* assuming that the session does not exist in the table.
*
* The [guid] column is used for matching an existing row because this column holds unique values
* and to avoid an unneeded database SELECT just to retrieve the primary key of a row.
*
* This function must be called in the context of a [transaction] block.
*/
private fun SQLiteDatabase.upsertSession(sessionId: String, contentValues: ContentValues) {
private fun SQLiteDatabase.upsertSession(guid: String, contentValues: ContentValues) {
val affectedRowsCount = updateRow(
tableName = SessionsTable.NAME,
contentValues = contentValues,
columnName = SESSION_ID,
columnValue = sessionId
columnName = GUID,
columnValue = guid
)
if (affectedRowsCount == 0) {
insert(
Expand All @@ -128,19 +132,21 @@ class SessionsDatabaseRepository(
}

/**
* Delete the session identified by the given [sessionId] from the table.
* Delete the session identified by the given [sessionGuid] from the table.
*/
private fun SQLiteDatabase.deleteSession(sessionId: String) = delete(
private fun SQLiteDatabase.deleteSession(sessionGuid: String) = delete(
tableName = SessionsTable.NAME,
columnName = SESSION_ID,
columnValue = sessionId
columnName = GUID,
columnValue = sessionGuid
)

fun querySessionBySessionId(sessionId: String): Session {
return try {
query {
read(SessionsTable.NAME,
selection = "$SESSION_ID=?",
// The value of "sessionId" is replaced with the value of the "_id"
// column when the session is queried initially to guarantee a unique value.
selection = "$_ID=?",
selectionArgs = arrayOf(sessionId))
}.first()
} catch (e: NoSuchElementException) {
Expand Down Expand Up @@ -192,7 +198,11 @@ class SessionsDatabaseRepository(
Session.RECORDING_OPT_OUT_ON

Session(
sessionId = cursor.getString(SESSION_ID),
// The value of the auto-incrementing primary key "_id" column is written as
// the value of the "sessionId" field as the first step to rely on the "guid"
// column. It has a unique value which is not guaranteed by "sessionId".
sessionId = "${cursor.getLong(_ID)}",
guid = cursor.getString(GUID),
abstractt = cursor.getString(ABSTRACT),
date = cursor.getString(DATE),
dateUTC = cursor.getLong(DATE_UTC),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ public class SessionsDBOpenHelper extends SQLiteOpenHelper {

private static final String SESSIONS_TABLE_CREATE =
"CREATE TABLE " + SessionsTable.NAME + " (" +
BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Columns.SESSION_ID + " TEXT, " +
Columns.GUID + " TEXT NOT NULL UNIQUE, " +
Columns.TITLE + " TEXT, " +
Columns.SUBTITLE + " TEXT, " +
Columns.DAY + " INTEGER, " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import info.metadude.android.eventfahrplan.network.serialization.FahrplanParser
data class Session(

var sessionId: String = "",
var guid: String = "",

var abstractt: String = "",
var dayIndex: Int = 0,
var date: String = "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
import java.util.List;

import info.metadude.android.eventfahrplan.commons.logging.Logging;
import info.metadude.android.eventfahrplan.network.models.Session;
import info.metadude.android.eventfahrplan.network.models.Meta;
import info.metadude.android.eventfahrplan.network.models.Session;
import info.metadude.android.eventfahrplan.network.serialization.exceptions.MissingXmlAttributeException;
import info.metadude.android.eventfahrplan.network.temporal.DateParser;
import info.metadude.android.eventfahrplan.network.validation.DateFieldValidation;
Expand Down Expand Up @@ -173,8 +173,10 @@ private Boolean parseFahrplan(String fahrplan, String eTag) {
}
if (name.equalsIgnoreCase("event")) {
String id = parser.getAttributeValue(null, "id");
String guid = parser.getAttributeValue(null, "guid");
Session session = new Session();
session.setSessionId(id);
session.setGuid(guid);
session.setDayIndex(day);
session.setRoom(room);
session.setDate(date);
Expand Down

0 comments on commit 1c895b3

Please sign in to comment.