Skip to content

Commit

Permalink
Changed column type of timestamp in LocalChangeEntity (#2101)
Browse files Browse the repository at this point in the history
* Changed column type of timestamp in LocalChangeEntity

* Fixed compilation issues

* Updated kdoc

* Update engine/src/main/java/com/google/android/fhir/LocalChange.kt

Co-authored-by: Omar Ismail <[email protected]>

---------

Co-authored-by: Omar Ismail <[email protected]>
  • Loading branch information
aditya-07 and omarismail94 committed Jul 31, 2023
1 parent 65ddc7f commit a26dcbe
Show file tree
Hide file tree
Showing 12 changed files with 1,066 additions and 36 deletions.
941 changes: 941 additions & 0 deletions engine/schemas/com.google.android.fhir.db.impl.ResourceDatabase/6.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import androidx.room.testing.MigrationTestHelper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import ca.uhn.fhir.context.FhirContext
import com.google.android.fhir.db.impl.entities.LocalChangeEntity
import com.google.android.fhir.toTimeZoneString
import com.google.common.truth.Truth.assertThat
import java.io.IOException
import java.util.Date
Expand Down Expand Up @@ -172,13 +174,64 @@ class ResourceDatabaseMigrationTest {
assertThat(retrievedTask).isEqualTo(bedNetTask)
}

@Test
fun migrate5To6_should_execute_with_no_exception(): Unit = runBlocking {
val taskId = "bed-net-001"
val bedNetTask: String =
Task()
.apply {
id = taskId
description = "Issue bed net"
meta.lastUpdated = Date()
}
.let { iParser.encodeResourceToString(it) }

// Since the migration here is to change the column type of LocalChangeEntity.timestamp from
// string to Instant (integer). We are making sure that the data is migrated properly.
helper.createDatabase(DB_NAME, 5).apply {
val date = Date()
execSQL(
"INSERT INTO ResourceEntity (resourceUuid, resourceType, resourceId, serializedResource, lastUpdatedLocal) VALUES ('bed-net-001', 'Task', 'bed-net-001', '$bedNetTask', '${DbTypeConverters.instantToLong(date.toInstant())}' );"
)

execSQL(
"INSERT INTO LocalChangeEntity (resourceType, resourceId, timestamp, type, payload) VALUES ('Task', 'bed-net-001', '${date.toTimeZoneString()}', '${DbTypeConverters.localChangeTypeToInt(LocalChangeEntity.Type.INSERT)}', '$bedNetTask' );"
)
close()
}

helper.runMigrationsAndValidate(DB_NAME, 6, true, MIGRATION_5_6)

val retrievedTask: String?
val localChangeEntityTimeStamp: Long
val resourceEntityLastUpdatedLocal: Long
getMigratedRoomDatabase().apply {
retrievedTask = this.resourceDao().getResource(taskId, ResourceType.Task)
resourceEntityLastUpdatedLocal =
query("Select lastUpdatedLocal from ResourceEntity", null).let {
it.moveToFirst()
it.getLong(0)
}
localChangeEntityTimeStamp =
query("Select timestamp from LocalChangeEntity", null).let {
it.moveToFirst()
it.getLong(0)
}

openHelper.writableDatabase.close()
}

assertThat(retrievedTask).isEqualTo(bedNetTask)
assertThat(localChangeEntityTimeStamp).isEqualTo(resourceEntityLastUpdatedLocal)
}

private fun getMigratedRoomDatabase(): ResourceDatabase =
Room.databaseBuilder(
InstrumentationRegistry.getInstrumentation().targetContext,
ResourceDatabase::class.java,
DB_NAME
)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6)
.build()

companion object {
Expand Down
7 changes: 4 additions & 3 deletions engine/src/main/java/com/google/android/fhir/LocalChange.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 Google LLC
* Copyright 2022-2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@
package com.google.android.fhir

import com.google.android.fhir.db.impl.dao.LocalChangeToken
import java.time.Instant
import org.hl7.fhir.r4.model.Resource

/** Data class for squashed local changes for resource */
Expand All @@ -27,8 +28,8 @@ data class LocalChange(
val resourceId: String,
/** This is the id of the version of the resource that this local change is based of */
val versionId: String? = null,
/** last updated timestamp on server when this local changes are sync with server */
val timestamp: String = "",
/** The time instant the app user performed a CUD operation on the resource. */
val timestamp: Instant,
/** Type of local change like insert, delete, etc */
val type: Type,
/** json string with local changes */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ internal class DatabaseImpl(
}
}

addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6)
}
.build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import com.google.android.fhir.db.impl.entities.UriIndexEntity
LocalChangeEntity::class,
PositionIndexEntity::class
],
version = 5,
version = 6,
exportSchema = true
)
@TypeConverters(DbTypeConverters::class)
Expand Down Expand Up @@ -108,3 +108,21 @@ val MIGRATION_4_5 =
)
}
}

/** Changes column type of [LocalChangeEntity.timestamp] from [String] to [java.time.Instant]. */
val MIGRATION_5_6 =
object : Migration(/* startVersion = */ 5, /* endVersion = */ 6) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"CREATE TABLE IF NOT EXISTS `_new_LocalChangeEntity` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceType` TEXT NOT NULL, `resourceId` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `type` INTEGER NOT NULL, `payload` TEXT NOT NULL, `versionId` TEXT)"
)
database.execSQL(
"INSERT INTO `_new_LocalChangeEntity` (`id`,`resourceType`,`resourceId`,`timestamp`,`type`,`payload`,`versionId`) SELECT `id`,`resourceType`,`resourceId`, strftime('%s', `timestamp`) || substr(strftime('%f', `timestamp`), 4),`type`,`payload`,`versionId` FROM `LocalChangeEntity`"
)
database.execSQL("DROP TABLE `LocalChangeEntity`")
database.execSQL("ALTER TABLE `_new_LocalChangeEntity` RENAME TO `LocalChangeEntity`")
database.execSQL(
"CREATE INDEX IF NOT EXISTS `index_LocalChangeEntity_resourceType_resourceId` ON `LocalChangeEntity` (`resourceType`, `resourceId`)"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import com.google.android.fhir.db.impl.entities.LocalChangeEntity
import com.google.android.fhir.db.impl.entities.LocalChangeEntity.Type
import com.google.android.fhir.db.impl.entities.ResourceEntity
import com.google.android.fhir.logicalId
import com.google.android.fhir.toTimeZoneString
import com.google.android.fhir.versionId
import java.time.Instant
import java.util.Date
Expand All @@ -50,15 +49,14 @@ internal abstract class LocalChangeDao {
open suspend fun addInsert(resource: Resource, timeOfLocalChange: Instant) {
val resourceId = resource.logicalId
val resourceType = resource.resourceType
val timestamp = Date.from(timeOfLocalChange).toTimeZoneString()
val resourceString = iParser.encodeResourceToString(resource)

addLocalChange(
LocalChangeEntity(
id = 0,
resourceType = resourceType.name,
resourceId = resourceId,
timestamp = timestamp,
timestamp = timeOfLocalChange,
type = Type.INSERT,
payload = resourceString,
versionId = resource.versionId
Expand All @@ -69,7 +67,6 @@ internal abstract class LocalChangeDao {
suspend fun addUpdate(oldEntity: ResourceEntity, resource: Resource, timeOfLocalChange: Instant) {
val resourceId = resource.logicalId
val resourceType = resource.resourceType
val timestamp = Date.from(timeOfLocalChange).toTimeZoneString()

if (!localChangeIsEmpty(resourceId, resourceType) &&
lastChangeType(resourceId, resourceType)!! == Type.DELETE
Expand All @@ -96,7 +93,7 @@ internal abstract class LocalChangeDao {
id = 0,
resourceType = resourceType.name,
resourceId = resourceId,
timestamp = timestamp,
timestamp = timeOfLocalChange,
type = Type.UPDATE,
payload = jsonDiff.toString(),
versionId = oldEntity.versionId
Expand All @@ -105,13 +102,12 @@ internal abstract class LocalChangeDao {
}

suspend fun addDelete(resourceId: String, resourceType: ResourceType, remoteVersionId: String?) {
val timestamp = Date().toTimeZoneString()
addLocalChange(
LocalChangeEntity(
id = 0,
resourceType = resourceType.name,
resourceId = resourceId,
timestamp = timestamp,
timestamp = Date().toInstant(),
type = Type.DELETE,
payload = "",
versionId = remoteVersionId
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 Google LLC
* Copyright 2022-2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,6 +19,7 @@ package com.google.android.fhir.db.impl.entities
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import java.time.Instant

/**
* When a local change to a resource happens, the lastUpdated timestamp in [ResourceEntity] is
Expand Down Expand Up @@ -55,7 +56,7 @@ internal data class LocalChangeEntity(
@PrimaryKey(autoGenerate = true) val id: Long,
val resourceType: String,
val resourceId: String,
val timestamp: String = "",
val timestamp: Instant,
val type: Type,
val payload: String,
val versionId: String? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import com.google.android.fhir.sync.UploadRequest
import com.google.android.fhir.sync.UrlDownloadRequest
import com.google.common.truth.Truth.assertThat
import java.net.SocketTimeoutException
import java.time.Instant
import java.time.OffsetDateTime
import java.util.Date
import java.util.LinkedList
Expand Down Expand Up @@ -173,7 +174,8 @@ object TestFhirEngineImpl : FhirEngine {
resourceId = id,
payload = "{}",
token = LocalChangeToken(listOf()),
type = LocalChange.Type.INSERT
type = LocalChange.Type.INSERT,
timestamp = Instant.now()
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.google.android.fhir.testing.readFromFile
import com.google.android.fhir.testing.readJsonArrayFromFile
import com.google.android.fhir.versionId
import com.google.common.truth.Truth.assertThat
import java.time.Instant
import java.util.Date
import junit.framework.TestCase
import kotlinx.coroutines.runBlocking
Expand Down Expand Up @@ -62,7 +63,8 @@ class LocalChangeUtilsTest : TestCase() {
}
)
}
)
),
timestamp = Instant.now()
)

val localChange = localChangeEntity.toLocalChange()
Expand Down Expand Up @@ -158,7 +160,8 @@ class LocalChangeUtilsTest : TestCase() {
type = LocalChange.Type.UPDATE,
payload = jsonDiff.toString(),
versionId = oldEntity.versionId,
token = LocalChangeToken(listOf(currentChangeId + 1))
token = LocalChangeToken(listOf(currentChangeId + 1)),
timestamp = Instant.now()
)
}

Expand All @@ -169,7 +172,8 @@ class LocalChangeUtilsTest : TestCase() {
type = LocalChange.Type.INSERT,
payload = jsonParser.encodeResourceToString(entity),
versionId = entity.versionId,
token = LocalChangeToken(listOf(1L))
token = LocalChangeToken(listOf(1L)),
timestamp = Instant.now()
)
}

Expand All @@ -183,7 +187,8 @@ class LocalChangeUtilsTest : TestCase() {
type = LocalChange.Type.DELETE,
payload = "",
versionId = entity.versionId,
token = LocalChangeToken(listOf(currentChangeId + 1))
token = LocalChangeToken(listOf(currentChangeId + 1)),
timestamp = Instant.now()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ class FhirEngineImplTest {
filter(
LOCAL_LAST_UPDATED_PARAM,
{
value = of(DateTimeType(localChangeTimestamp))
value = of(DateTimeType(Date.from(localChangeTimestamp)))
prefix = ParamPrefixEnum.EQUAL
}
)
Expand Down Expand Up @@ -607,14 +607,14 @@ class FhirEngineImplTest {
filter(
LOCAL_LAST_UPDATED_PARAM,
{
value = of(DateTimeType(localChangeTimestampWhenUpdated))
value = of(DateTimeType(Date.from(localChangeTimestampWhenUpdated)))
prefix = ParamPrefixEnum.EQUAL
}
)
}

assertThat(DateTimeType(localChangeTimestampWhenUpdated).value)
.isAtLeast(DateTimeType(localChangeTimestampWhenCreated).value)
assertThat(DateTimeType(Date.from(localChangeTimestampWhenUpdated)).value)
.isAtLeast(DateTimeType(Date.from(localChangeTimestampWhenCreated)).value)
assertThat(result).isNotEmpty()
assertThat(result.map { it.logicalId }).containsExactly("patient-id-update").inOrder()
}
Expand Down
Loading

0 comments on commit a26dcbe

Please sign in to comment.