Skip to content

Commit

Permalink
Allow sorting in [rev]include results. (#2200)
Browse files Browse the repository at this point in the history
* Allow sorting in [rev]include results

* Review comment changes

* Fixed failing tests

* Lint fails build because of the use of restricted api  from  package. So, supressing it as couldn't find a workaround for the api

* Spotless

* Updated failing test

* Review comments: Fixed tests

* Review comments: Refactored db code to separate out search functions for forward and rev include

* Lint fails build because of the use of restricted api UUIDUtil.convertUUIDToByte from androidx.room package. So, supressing it as couldn't find a workaround for the api

* Review Comments: Unified the usage of getSortOrder

* Review comments: Updated kdoc
  • Loading branch information
aditya-07 committed Dec 26, 2023
1 parent 977ecfa commit 8628708
Show file tree
Hide file tree
Showing 9 changed files with 1,287 additions and 366 deletions.

Large diffs are not rendered by default.

40 changes: 1 addition & 39 deletions engine/src/main/java/com/google/android/fhir/FhirEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -153,42 +153,4 @@ data class SearchResult<R : Resource>(
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>>?,
) {
override fun equals(other: Any?) =
other is SearchResult<*> &&
equalsShallow(resource, other.resource) &&
equalsShallow(included, other.included) &&
equalsShallow(revIncluded, other.revIncluded)

private fun equalsShallow(first: Resource, second: Resource) =
first.resourceType == second.resourceType && first.logicalId == second.logicalId

private fun equalsShallow(first: List<Resource>, second: List<Resource>) =
first.size == second.size &&
first.asSequence().zip(second.asSequence()).all { (x, y) -> equalsShallow(x, y) }

private fun equalsShallow(
first: 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) ->
x.key == y.key && equalsShallow(x.value, y.value)
}
} else {
first?.size == second?.size
}

@JvmName("equalsShallowRevInclude")
private fun equalsShallow(
first: 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) ->
x.key == y.key && equalsShallow(x.value, y.value)
}
} else {
first?.size == second?.size
}
}
)
14 changes: 11 additions & 3 deletions engine/src/main/java/com/google/android/fhir/db/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ package com.google.android.fhir.db

import com.google.android.fhir.LocalChange
import com.google.android.fhir.LocalChangeToken
import com.google.android.fhir.db.impl.dao.IndexedIdAndResource
import com.google.android.fhir.db.impl.dao.ForwardIncludeSearchResult
import com.google.android.fhir.db.impl.dao.ReverseIncludeSearchResult
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 @@ -94,9 +95,11 @@ internal interface Database {
*/
suspend fun delete(type: ResourceType, id: String)

suspend fun <R : Resource> search(query: SearchQuery): List<R>
suspend fun <R : Resource> search(query: SearchQuery): List<ResourceWithUUID<R>>

suspend fun searchReferencedResources(query: SearchQuery): List<IndexedIdAndResource>
suspend fun searchForwardReferencedResources(query: SearchQuery): List<ForwardIncludeSearchResult>

suspend fun searchReverseReferencedResources(query: SearchQuery): List<ReverseIncludeSearchResult>

suspend fun count(query: SearchQuery): Long

Expand Down Expand Up @@ -183,3 +186,8 @@ internal interface Database {
*/
suspend fun purge(type: ResourceType, id: String, forcePurge: Boolean = false)
}

data class ResourceWithUUID<R>(
val uuid: UUID,
val resource: R,
)
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ import com.google.android.fhir.DatabaseErrorStrategy
import com.google.android.fhir.LocalChange
import com.google.android.fhir.LocalChangeToken
import com.google.android.fhir.db.ResourceNotFoundException
import com.google.android.fhir.db.ResourceWithUUID
import com.google.android.fhir.db.impl.DatabaseImpl.Companion.UNENCRYPTED_DATABASE_NAME
import com.google.android.fhir.db.impl.dao.IndexedIdAndResource
import com.google.android.fhir.db.impl.dao.ForwardIncludeSearchResult
import com.google.android.fhir.db.impl.dao.ReverseIncludeSearchResult
import com.google.android.fhir.db.impl.entities.ResourceEntity
import com.google.android.fhir.index.ResourceIndexer
import com.google.android.fhir.logicalId
Expand Down Expand Up @@ -199,23 +201,43 @@ internal class DatabaseImpl(
}
}

override suspend fun <R : Resource> search(query: SearchQuery): List<R> {
override suspend fun <R : Resource> search(
query: SearchQuery,
): List<ResourceWithUUID<R>> {
return db.withTransaction {
resourceDao
.getResources(SimpleSQLiteQuery(query.query, query.args.toTypedArray()))
.map { iParser.parseResource(it) as R }
.distinctBy { it.id }
.map { ResourceWithUUID(it.uuid, iParser.parseResource(it.serializedResource) as R) }
.distinctBy { it.uuid }
}
}

override suspend fun searchReferencedResources(query: SearchQuery): List<IndexedIdAndResource> {
override suspend fun searchForwardReferencedResources(
query: SearchQuery,
): List<ForwardIncludeSearchResult> {
return db.withTransaction {
resourceDao
.getReferencedResources(SimpleSQLiteQuery(query.query, query.args.toTypedArray()))
.getForwardReferencedResources(SimpleSQLiteQuery(query.query, query.args.toTypedArray()))
.map {
IndexedIdAndResource(
ForwardIncludeSearchResult(
it.matchingIndex,
it.idOfBaseResourceOnWhichThisMatchedInc ?: it.idOfBaseResourceOnWhichThisMatchedRev!!,
it.baseResourceUUID,
iParser.parseResource(it.serializedResource) as Resource,
)
}
}
}

override suspend fun searchReverseReferencedResources(
query: SearchQuery,
): List<ReverseIncludeSearchResult> {
return db.withTransaction {
resourceDao
.getReverseReferencedResources(SimpleSQLiteQuery(query.query, query.args.toTypedArray()))
.map {
ReverseIncludeSearchResult(
it.matchingIndex,
it.baseResourceTypeAndId,
iParser.parseResource(it.serializedResource) as Resource,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,18 @@ internal abstract class ResourceDao {
resourceUuid: UUID,
): ResourceEntity?

@RawQuery abstract suspend fun getResources(query: SupportSQLiteQuery): List<String>
@RawQuery
abstract suspend fun getResources(query: SupportSQLiteQuery): List<SerializedResourceWithUuid>

@RawQuery
abstract suspend fun getReferencedResources(
abstract suspend fun getForwardReferencedResources(
query: SupportSQLiteQuery,
): List<IndexedIdAndSerializedResource>
): List<ForwardIncludeSearchResponse>

@RawQuery
abstract suspend fun getReverseReferencedResources(
query: SupportSQLiteQuery,
): List<ReverseIncludeSearchResponse>

@RawQuery abstract suspend fun countResources(query: SupportSQLiteQuery): Long

Expand Down Expand Up @@ -411,23 +417,39 @@ internal abstract class ResourceDao {
}
}

/**
* Data class representing the value returned by [getReferencedResources]. The optional fields may
* or may-not contain values based on the search query.
*/
internal data class IndexedIdAndSerializedResource(
internal class ForwardIncludeSearchResponse(
@ColumnInfo(name = "index_name") val matchingIndex: String,
@ColumnInfo(name = "resourceUuid") val baseResourceUUID: UUID,
val serializedResource: String,
)

internal class ReverseIncludeSearchResponse(
@ColumnInfo(name = "index_name") val matchingIndex: String,
@ColumnInfo(name = "index_value") val idOfBaseResourceOnWhichThisMatchedRev: String?,
@ColumnInfo(name = "resourceId") val idOfBaseResourceOnWhichThisMatchedInc: String?,
@ColumnInfo(name = "index_value") val baseResourceTypeAndId: String,
val serializedResource: String,
)

/**
* Data class representing an included or revIncluded [Resource], index on which the match was done
* and the id of the base [Resource] for which this [Resource] has been included.
* Data class representing a forward included [Resource], index on which the match was done and the
* uuid of the base [Resource] for which this [Resource] has been included.
*/
internal data class IndexedIdAndResource(
val matchingIndex: String,
val idOfBaseResourceOnWhichThisMatched: String,
internal data class ForwardIncludeSearchResult(
val searchIndex: String,
val baseResourceUUID: UUID,
val resource: Resource,
)

/**
* Data class representing a reverse included [Resource], index on which the match was done and the
* type and logical id of the base [Resource] for which this [Resource] has been included.
*/
internal data class ReverseIncludeSearchResult(
val searchIndex: String,
val baseResourceTypeWithId: String,
val resource: Resource,
)

internal data class SerializedResourceWithUuid(
@ColumnInfo(name = "resourceUuid") val uuid: UUID,
val serializedResource: String,
)
Loading

0 comments on commit 8628708

Please sign in to comment.