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

Add a LocalChangeFetcher #2135

Merged
merged 18 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from 14 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.

26 changes: 16 additions & 10 deletions engine/src/main/java/com/google/android/fhir/FhirEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.google.android.fhir
import com.google.android.fhir.db.ResourceNotFoundException
import com.google.android.fhir.search.Search
import com.google.android.fhir.sync.ConflictResolver
import com.google.android.fhir.sync.upload.LocalChangesFetchMode
import java.time.OffsetDateTime
import kotlinx.coroutines.flow.Flow
import org.hl7.fhir.r4.model.Resource
Expand Down Expand Up @@ -53,7 +54,8 @@ interface FhirEngine {
* api caller should [Flow.collect] it.
*/
suspend fun syncUpload(
upload: (suspend (List<LocalChange>) -> Flow<Pair<LocalChangeToken, Resource>>)
localChangesFetchMode: LocalChangesFetchMode,
upload: (suspend (List<LocalChange>) -> Flow<Pair<LocalChangeToken, Resource>>),
)

/**
Expand All @@ -62,7 +64,7 @@ interface FhirEngine {
*/
suspend fun syncDownload(
conflictResolver: ConflictResolver,
download: suspend () -> Flow<List<Resource>>
download: suspend () -> Flow<List<Resource>>,
)

/**
Expand All @@ -87,29 +89,32 @@ interface FhirEngine {
* Retrieves a list of [LocalChange]s for [Resource] with given type and id, which can be used to
* purge resource from database. If there is no local change for given [resourceType] and
* [Resource.id], return an empty list.
*
* @param type The [ResourceType]
* @param id The resource id [Resource.id]
* @return [List]<[LocalChange]> A list of local changes for given [resourceType] and
* [Resource.id] . If there is no local change for given [resourceType] and [Resource.id], return
* an empty list.
* [Resource.id] . If there is no local change for given [resourceType] and [Resource.id],
* return an empty list.
*/
suspend fun getLocalChanges(type: ResourceType, id: String): List<LocalChange>

/**
* Purges a resource from the database based on resource type and id without any deletion of data
* from the server.
*
* @param type The [ResourceType]
* @param id The resource id [Resource.id]
* @param isLocalPurge default value is false here resource will not be deleted from
* LocalChangeEntity table but it will throw IllegalStateException("Resource has local changes
* either sync with server or FORCE_PURGE required") if local change exists. If true this API will
* delete resource entry from LocalChangeEntity table.
* LocalChangeEntity table but it will throw IllegalStateException("Resource has local changes
* either sync with server or FORCE_PURGE required") if local change exists. If true this API
* will delete resource entry from LocalChangeEntity table.
*/
suspend fun purge(type: ResourceType, id: String, forcePurge: Boolean = false)
}

/**
* Returns a FHIR resource of type [R] with [id] from the local storage.
*
* @param <R> The resource type which should be a subtype of [Resource].
* @throws ResourceNotFoundException if the resource is not found
*/
Expand All @@ -120,6 +125,7 @@ suspend inline fun <reified R : Resource> FhirEngine.get(id: String): R {

/**
* Deletes a FHIR resource of type [R] with [id] from the local storage.
*
* @param <R> The resource type which should be a subtype of [Resource].
*/
suspend inline fun <reified R : Resource> FhirEngine.delete(id: String) {
Expand All @@ -138,7 +144,7 @@ data class SearchResult<R : Resource>(
/** Matching referenced resources as per the [Search.include] criteria in the query. */
val included: Map<SearchParamName, List<Resource>>?,
/** Matching referenced resources as per the [Search.revInclude] criteria in the query. */
val revIncluded: Map<Pair<ResourceType, SearchParamName>, List<Resource>>?
val revIncluded: Map<Pair<ResourceType, SearchParamName>, List<Resource>>?,
) {
override fun equals(other: Any?) =
other is SearchResult<*> &&
Expand All @@ -155,7 +161,7 @@ data class SearchResult<R : Resource>(

private fun equalsShallow(
first: Map<SearchParamName, List<Resource>>?,
second: Map<SearchParamName, List<Resource>>?
second: Map<SearchParamName, List<Resource>>?,
) =
if (first != null && second != null && first.size == second.size) {
first.entries.asSequence().zip(second.entries.asSequence()).all { (x, y) ->
Expand All @@ -168,7 +174,7 @@ data class SearchResult<R : Resource>(
@JvmName("equalsShallowRevInclude")
private fun equalsShallow(
first: Map<Pair<ResourceType, SearchParamName>, List<Resource>>?,
second: Map<Pair<ResourceType, SearchParamName>, List<Resource>>?
second: Map<Pair<ResourceType, SearchParamName>, List<Resource>>?,
) =
if (first != null && second != null && first.size == second.size) {
first.entries.asSequence().zip(second.entries.asSequence()).all { (x, y) ->
Expand Down
17 changes: 11 additions & 6 deletions engine/src/main/java/com/google/android/fhir/db/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ internal interface Database {
resourceId: String,
resourceType: ResourceType,
versionId: String,
lastUpdated: Instant
lastUpdated: Instant,
)

/**
Expand Down Expand Up @@ -105,6 +105,9 @@ internal interface Database {
*/
suspend fun getAllLocalChanges(): List<LocalChange>

/** Retrieves the count of [LocalChange]s stored in the database. */
suspend fun getLocalChangesCount(): Int

/** Remove the [LocalChangeEntity] s with given ids. Call this after a successful sync. */
suspend fun deleteUpdates(token: LocalChangeToken)

Expand All @@ -127,23 +130,25 @@ internal interface Database {
* Retrieve a list of [LocalChange] for [Resource] with given type and id, which can be used to
* purge resource from database. If there is no local change for given [resourceType] and
* [Resource.id], return an empty list.
*
* @param type The [ResourceType]
* @param id The resource id [Resource.id]
* @return [List]<[LocalChange]> A list of local changes for given [resourceType] and
* [Resource.id] . If there is no local change for given [resourceType] and [Resource.id], return
* empty list.
* [Resource.id] . If there is no local change for given [resourceType] and [Resource.id],
* return empty list.
*/
suspend fun getLocalChanges(type: ResourceType, id: String): List<LocalChange>

/**
* Purge resource from database based on resource type and id without any deletion of data from
* the server.
*
* @param type The [ResourceType]
* @param id The resource id [Resource.id]
* @param isLocalPurge default value is false here resource will not be deleted from
* LocalChangeEntity table but it will throw IllegalStateException("Resource has local changes
* either sync with server or FORCE_PURGE required") if local change exists. If true this API will
* delete resource entry from LocalChangeEntity table.
* LocalChangeEntity table but it will throw IllegalStateException("Resource has local changes
* either sync with server or FORCE_PURGE required") if local change exists. If true this API
* will delete resource entry from LocalChangeEntity table.
*/
suspend fun purge(type: ResourceType, id: String, forcePurge: Boolean = false)
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ internal class DatabaseImpl(
private val context: Context,
private val iParser: IParser,
databaseConfig: DatabaseConfig,
private val resourceIndexer: ResourceIndexer
private val resourceIndexer: ResourceIndexer,
) : com.google.android.fhir.db.Database {

val db: ResourceDatabase
Expand Down Expand Up @@ -88,8 +88,10 @@ internal class DatabaseImpl(
openHelperFactory {
SQLCipherSupportHelper(
it,
databaseErrorStrategy = databaseConfig.databaseErrorStrategy
) { DatabaseEncryptionKeyProvider.getOrCreatePassphrase(DATABASE_PASSPHRASE_NAME) }
databaseErrorStrategy = databaseConfig.databaseErrorStrategy,
) {
DatabaseEncryptionKeyProvider.getOrCreatePassphrase(DATABASE_PASSPHRASE_NAME)
}
}
}

Expand All @@ -115,7 +117,7 @@ internal class DatabaseImpl(
val timeOfLocalChange = Instant.now()
localChangeDao.addInsert(it, timeOfLocalChange)
resourceDao.insertLocalResource(it, timeOfLocalChange)
}
},
)
}
return logicalIds
Expand All @@ -140,14 +142,14 @@ internal class DatabaseImpl(
resourceId: String,
resourceType: ResourceType,
versionId: String,
lastUpdated: Instant
lastUpdated: Instant,
) {
db.withTransaction {
resourceDao.updateAndIndexRemoteVersionIdAndLastUpdate(
resourceId,
resourceType,
versionId,
lastUpdated
lastUpdated,
)
}
}
Expand All @@ -174,12 +176,13 @@ internal class DatabaseImpl(
null
}
val rowsDeleted = resourceDao.deleteResource(resourceId = id, resourceType = type)
if (rowsDeleted > 0)
if (rowsDeleted > 0) {
localChangeDao.addDelete(
resourceId = id,
resourceType = type,
remoteVersionId = remoteVersionId
remoteVersionId = remoteVersionId,
)
}
}
}

Expand All @@ -200,7 +203,7 @@ internal class DatabaseImpl(
IndexedIdAndResource(
it.matchingIndex,
it.idOfBaseResourceOnWhichThisMatchedInc ?: it.idOfBaseResourceOnWhichThisMatchedRev!!,
iParser.parseResource(it.serializedResource) as Resource
iParser.parseResource(it.serializedResource) as Resource,
)
}
}
Expand All @@ -216,6 +219,10 @@ internal class DatabaseImpl(
return db.withTransaction { localChangeDao.getAllLocalChanges().map { it.toLocalChange() } }
}

override suspend fun getLocalChangesCount(): Int {
return db.withTransaction { localChangeDao.getLocalChangesCount() }
}

override suspend fun deleteUpdates(token: LocalChangeToken) {
db.withTransaction { localChangeDao.discardLocalChanges(token) }
}
Expand Down Expand Up @@ -265,12 +272,12 @@ internal class DatabaseImpl(
if (forcePurge) {
resourceDao.deleteResource(resourceId = id, resourceType = type)
localChangeDao.discardLocalChanges(
token = LocalChangeToken(localChangeEntityList.map { it.id })
token = LocalChangeToken(localChangeEntityList.map { it.id }),
)
} else {
// local change is available but FORCE_PURGE = false then throw exception
throw IllegalStateException(
"Resource with type $type and id $id has local changes, either sync with server or FORCE_PURGE required"
"Resource with type $type and id $id has local changes, either sync with server or FORCE_PURGE required",
)
}
}
Expand Down Expand Up @@ -301,5 +308,5 @@ internal class DatabaseImpl(
data class DatabaseConfig(
val inMemory: Boolean,
val enableEncryption: Boolean,
val databaseErrorStrategy: DatabaseErrorStrategy
val databaseErrorStrategy: DatabaseErrorStrategy,
)
Loading