Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start saving local lastUpdated to resources for search operations. #2030

Merged
merged 17 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.google.android.fhir.db.ResourceNotFoundException
import com.google.android.fhir.db.impl.dao.toLocalChange
import com.google.android.fhir.db.impl.entities.LocalChangeEntity
import com.google.android.fhir.logicalId
import com.google.android.fhir.search.LOCAL_LAST_UPDATED_PARAM
import com.google.android.fhir.search.Operation
import com.google.android.fhir.search.Order
import com.google.android.fhir.search.Search
Expand Down Expand Up @@ -3075,6 +3076,33 @@ class DatabaseImplTest {
assertThat(result.map { it.nameFirstRep.nameAsSingleString }).contains("Darcy Smith")
}

@Test
fun search_patient_with_local_lastUpdated() = runBlocking {
database.insert(
Patient().apply { id = "patient-test-001" },
Patient().apply { id = "patient-test-002" },
Patient().apply { id = "patient-test-003" }
)

database.update(
Patient().apply {
id = "patient-test-002"
gender = Enumerations.AdministrativeGender.FEMALE
}
)

val result =
database.search<Patient>(
Search(ResourceType.Patient)
.apply { sort(LOCAL_LAST_UPDATED_PARAM, Order.DESCENDING) }
.getQuery()
)

assertThat(result.map { it.logicalId })
.containsAtLeast("patient-test-002", "patient-test-003", "patient-test-001")
.inOrder()
}

private companion object {
const val mockEpochTimeStamp = 1628516301000
const val TEST_PATIENT_1_ID = "test_patient_1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,45 @@ class ResourceDatabaseMigrationTest {

assertThat(retrievedTask).isEqualTo(bedNetTask)
}

@Test
fun migrate4To5_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) }

helper.createDatabase(DB_NAME, 4).apply {
execSQL(
"INSERT INTO ResourceEntity (resourceUuid, resourceType, resourceId, serializedResource) VALUES ('bed-net-001', 'Task', 'bed-net-001', '$bedNetTask');"
)
close()
}

// Re-open the database with version 4 and provide MIGRATION_3_4 as the migration process.
helper.runMigrationsAndValidate(DB_NAME, 5, true, MIGRATION_4_5)

val retrievedTask: String?
getMigratedRoomDatabase().apply {
retrievedTask = this.resourceDao().getResource(taskId, ResourceType.Task)
openHelper.writableDatabase.close()
}

assertThat(retrievedTask).isEqualTo(bedNetTask)
}

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

companion object {
Expand Down
9 changes: 9 additions & 0 deletions engine/src/main/java/com/google/android/fhir/db/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

package com.google.android.fhir.db

import androidx.annotation.VisibleForTesting
import com.google.android.fhir.db.impl.dao.LocalChangeToken
import com.google.android.fhir.db.impl.dao.SquashedLocalChange
import com.google.android.fhir.db.impl.entities.DateTimeIndexEntity
import com.google.android.fhir.db.impl.entities.LocalChangeEntity
import com.google.android.fhir.db.impl.entities.ResourceEntity
import com.google.android.fhir.search.SearchQuery
Expand Down Expand Up @@ -145,4 +147,11 @@ internal interface Database {
* delete resource entry from LocalChangeEntity table.
*/
suspend fun purge(type: ResourceType, id: String, forcePurge: Boolean = false)

/** @return DateTimeIndexEntities for the resource. */
@VisibleForTesting
suspend fun getDateTimeIndexEntities(
aditya-07 marked this conversation as resolved.
Show resolved Hide resolved
resourceId: String,
resourceType: ResourceType
): List<DateTimeIndexEntity>
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ internal class DatabaseImpl(
}
}

addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
}
.build()
}
Expand All @@ -110,22 +110,28 @@ internal class DatabaseImpl(
override suspend fun <R : Resource> insert(vararg resource: R): List<String> {
val logicalIds = mutableListOf<String>()
db.withTransaction {
logicalIds.addAll(resourceDao.insertAll(resource.toList()))
localChangeDao.addInsertAll(resource.toList())
logicalIds.addAll(
resource.map {
val timeOfLocalChange = Instant.now()
localChangeDao.addInsert(it, timeOfLocalChange)
resourceDao.insertResourceLocal(it, timeOfLocalChange)
}
)
}
return logicalIds
}

override suspend fun <R : Resource> insertRemote(vararg resource: R) {
db.withTransaction { resourceDao.insertAll(resource.toList()) }
db.withTransaction { resourceDao.insertAllRemote(resource.toList()) }
}

override suspend fun update(vararg resources: Resource) {
db.withTransaction {
resources.forEach {
val timeOfLocalChange = Instant.now()
val oldResourceEntity = selectEntity(it.resourceType, it.logicalId)
resourceDao.update(it)
localChangeDao.addUpdate(oldResourceEntity, it)
resourceDao.update(it, timeOfLocalChange)
localChangeDao.addUpdate(oldResourceEntity, it, timeOfLocalChange)
}
}
}
Expand All @@ -137,7 +143,7 @@ internal class DatabaseImpl(
lastUpdated: Instant
) {
db.withTransaction {
resourceDao.updateRemoteVersionIdAndLastUpdate(
resourceDao.updateAndIndexRemoteVersionIdAndLastUpdate(
resourceId,
resourceType,
versionId,
Expand Down Expand Up @@ -274,6 +280,12 @@ internal class DatabaseImpl(
}
}

override suspend fun getDateTimeIndexEntities(resourceId: String, resourceType: ResourceType) =
aditya-07 marked this conversation as resolved.
Show resolved Hide resolved
resourceDao.getResourceEntity(resourceId, resourceType)?.let {
resourceDao.getDateTimeIndexes(it.resourceUuid)
}
?: emptyList()

companion object {
/**
* The name for unencrypted database.
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 = 4,
version = 5,
exportSchema = true
)
@TypeConverters(DbTypeConverters::class)
Expand Down Expand Up @@ -99,3 +99,12 @@ val MIGRATION_3_4 =
)
}
}

val MIGRATION_4_5 =
object : Migration(/* startVersion = */ 4, /* endVersion = */ 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
aditya-07 marked this conversation as resolved.
Show resolved Hide resolved
"ALTER TABLE `ResourceEntity` ADD COLUMN `lastUpdatedLocal` INTEGER DEFAULT NULL"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ 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
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.ResourceType
Expand All @@ -46,14 +47,10 @@ internal abstract class LocalChangeDao {
@Insert abstract suspend fun addLocalChange(localChangeEntity: LocalChangeEntity)

@Transaction
open suspend fun addInsertAll(resources: List<Resource>) {
resources.forEach { resource -> addInsert(resource) }
}

suspend fun addInsert(resource: Resource) {
open suspend fun addInsert(resource: Resource, timeOfLocalChange: Instant) {
val resourceId = resource.logicalId
val resourceType = resource.resourceType
val timestamp = Date().toTimeZoneString()
val timestamp = Date.from(timeOfLocalChange).toTimeZoneString()
val resourceString = iParser.encodeResourceToString(resource)

addLocalChange(
Expand All @@ -69,13 +66,13 @@ internal abstract class LocalChangeDao {
)
}

suspend fun addUpdate(oldEntity: ResourceEntity, resource: Resource) {
suspend fun addUpdate(oldEntity: ResourceEntity, resource: Resource, timeOfLocalChange: Instant) {
val resourceId = resource.logicalId
val resourceType = resource.resourceType
val timestamp = Date().toTimeZoneString()
val timestamp = Date.from(timeOfLocalChange).toTimeZoneString()

if (!localChangeIsEmpty(resourceId, resourceType) &&
lastChangeType(resourceId, resourceType)!!.equals(Type.DELETE)
lastChangeType(resourceId, resourceType)!! == Type.DELETE
) {
throw InvalidLocalChangeException(
"Unexpected DELETE when updating $resourceType/$resourceId. UPDATE failed."
Expand Down
Loading
Loading