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 5 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "6dd24dd7340c3808456a812933bc6838",
"identityHash": "229dd3fb6a2d9524db54c649e8ffb90d",
"entities": [
{
"tableName": "ResourceEntity",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `resourceId` TEXT NOT NULL, `serializedResource` TEXT NOT NULL, `versionId` TEXT, `lastUpdatedRemote` INTEGER)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `resourceId` TEXT NOT NULL, `serializedResource` TEXT NOT NULL, `versionId` TEXT, `lastUpdatedRemote` INTEGER, `lastUpdatedLocal` INTEGER)",
"fields": [
{
"fieldPath": "id",
Expand Down Expand Up @@ -49,6 +49,12 @@
"columnName": "lastUpdatedRemote",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lastUpdatedLocal",
"columnName": "lastUpdatedLocal",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
Expand Down Expand Up @@ -935,7 +941,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6dd24dd7340c3808456a812933bc6838')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '229dd3fb6a2d9524db54c649e8ffb90d')"
]
}
}
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
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 @@ -31,9 +31,12 @@ import com.google.android.fhir.db.impl.dao.SquashedLocalChange
import com.google.android.fhir.db.impl.entities.LocalChangeEntity
import com.google.android.fhir.db.impl.entities.ResourceEntity
import com.google.android.fhir.index.ResourceIndexer
import com.google.android.fhir.index.ResourceIndices
import com.google.android.fhir.logicalId
import com.google.android.fhir.search.SearchQuery
import java.time.Instant
import java.util.Date
import org.hl7.fhir.r4.model.InstantType
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.ResourceType

Expand Down Expand Up @@ -110,22 +113,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 @@ -143,6 +152,20 @@ internal class DatabaseImpl(
versionId,
lastUpdated
)
// update the remote lastUpdated index
val entity = selectEntity(resourceType, resourceId)
val indicesToUpdate =
ResourceIndices.Builder(resourceType, resourceId)
.apply {
addDateTimeIndex(
ResourceIndexer.createLastUpdatedIndex(
resourceType,
InstantType(Date.from(lastUpdated))
)
)
}
.build()
resourceDao.updateIndicesForResource(indicesToUpdate, resourceType, entity.resourceUuid)
}
}

Expand Down Expand Up @@ -274,6 +297,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 @@ -72,5 +72,8 @@ val MIGRATION_2_3 =
database.execSQL(
"CREATE INDEX IF NOT EXISTS `index_DateTimeIndexEntity_index_from` ON `DateTimeIndexEntity` (`index_from`)"
)
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, timeOfChange: Instant?) {
aditya-07 marked this conversation as resolved.
Show resolved Hide resolved
aditya-07 marked this conversation as resolved.
Show resolved Hide resolved
val resourceId = resource.logicalId
val resourceType = resource.resourceType
val timestamp = Date().toTimeZoneString()
val timestamp = Date.from(timeOfChange).toTimeZoneString()
aditya-07 marked this conversation as resolved.
Show resolved Hide resolved
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, timeOfChange: Instant) {
aditya-07 marked this conversation as resolved.
Show resolved Hide resolved
val resourceId = resource.logicalId
val resourceType = resource.resourceType
val timestamp = Date().toTimeZoneString()
val timestamp = Date.from(timeOfChange).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