Skip to content

Commit

Permalink
Allow developers to add custom search parameter to the engine. (#1778)
Browse files Browse the repository at this point in the history
* Added custom search paramter.

* Review changes : Injecting ResourceIndexer into ResourceDao
  • Loading branch information
aditya-07 committed Feb 6, 2023
1 parent eae8160 commit 5f17993
Show file tree
Hide file tree
Showing 11 changed files with 719 additions and 124 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ package com.google.android.fhir.db.impl
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.MediumTest
import ca.uhn.fhir.rest.gclient.StringClientParam
import ca.uhn.fhir.rest.param.ParamPrefixEnum
import com.google.android.fhir.DateProvider
import com.google.android.fhir.FhirServices
import com.google.android.fhir.LocalChange
import com.google.android.fhir.db.Database
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
Expand Down Expand Up @@ -50,7 +52,9 @@ import org.hl7.fhir.r4.model.DateTimeType
import org.hl7.fhir.r4.model.DateType
import org.hl7.fhir.r4.model.DecimalType
import org.hl7.fhir.r4.model.Enumerations
import org.hl7.fhir.r4.model.Extension
import org.hl7.fhir.r4.model.HumanName
import org.hl7.fhir.r4.model.Identifier
import org.hl7.fhir.r4.model.Immunization
import org.hl7.fhir.r4.model.Meta
import org.hl7.fhir.r4.model.Observation
Expand All @@ -60,6 +64,8 @@ import org.hl7.fhir.r4.model.Quantity
import org.hl7.fhir.r4.model.Reference
import org.hl7.fhir.r4.model.ResourceType
import org.hl7.fhir.r4.model.RiskAssessment
import org.hl7.fhir.r4.model.SearchParameter
import org.hl7.fhir.r4.model.StringType
import org.json.JSONArray
import org.junit.After
import org.junit.Assert.assertThrows
Expand All @@ -84,15 +90,28 @@ class DatabaseImplTest {
@JvmField @Parameterized.Parameter(0) var encrypted: Boolean = false

private val context: Context = ApplicationProvider.getApplicationContext()
private val services =
FhirServices.builder(context)
.inMemory()
.apply { if (encrypted) enableEncryptionIfSupported() }
.build()
private val testingUtils = TestingUtils(services.parser)
private val database = services.database

@Before fun setUp(): Unit = runBlocking { database.insert(TEST_PATIENT_1) }
private lateinit var services: FhirServices
private lateinit var testingUtils: TestingUtils
private lateinit var database: Database

@Before
fun setUp(): Unit = runBlocking {
buildFhirService()
database.insert(TEST_PATIENT_1)
}

private fun buildFhirService(customSearchParameter: List<SearchParameter>? = null) {
services =
FhirServices.builder(context)
.inMemory()
.apply {
if (encrypted) enableEncryptionIfSupported()
setSearchParameters(customSearchParameter)
}
.build()
database = services.database
testingUtils = TestingUtils(services.parser)
}

@After
fun tearDown() {
Expand Down Expand Up @@ -451,10 +470,13 @@ class DatabaseImplTest {
fun delete_nonExistent_shouldNotInsertLocalChange() = runBlocking {
database.delete(ResourceType.Patient, "nonexistent_patient")
assertThat(
database.getAllLocalChanges().map { it }.none {
it.localChange.type.equals(LocalChangeEntity.Type.DELETE) &&
it.localChange.resourceId.equals("nonexistent_patient")
}
database
.getAllLocalChanges()
.map { it }
.none {
it.localChange.type.equals(LocalChangeEntity.Type.DELETE) &&
it.localChange.resourceId.equals("nonexistent_patient")
}
)
.isTrue()
}
Expand All @@ -480,9 +502,10 @@ class DatabaseImplTest {
val patient: Patient = testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json")
database.insertRemote(patient)
assertThat(
database.getAllLocalChanges().map { it }.none {
it.localChange.resourceId.equals(patient.logicalId)
}
database
.getAllLocalChanges()
.map { it }
.none { it.localChange.resourceId.equals(patient.logicalId) }
)
.isTrue()
}
Expand Down Expand Up @@ -532,15 +555,17 @@ class DatabaseImplTest {
}
database.insert(patient)
services.fhirEngine.syncUpload { it ->
it.first { it.resourceId == "remote-patient-3" }.let {
flowOf(
it.token to
Patient().apply {
id = it.resourceId
meta = remoteMeta
}
)
}
it
.first { it.resourceId == "remote-patient-3" }
.let {
flowOf(
it.token to
Patient().apply {
id = it.resourceId
meta = remoteMeta
}
)
}
}
val selectedEntity = database.selectEntity(ResourceType.Patient, "remote-patient-3")
assertThat(selectedEntity.versionId).isEqualTo(remoteMeta.versionId)
Expand All @@ -552,9 +577,10 @@ class DatabaseImplTest {
val patient: Patient = testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json")
database.insertRemote(patient, TEST_PATIENT_2)
assertThat(
database.getAllLocalChanges().map { it }.none {
it.localChange.resourceId in listOf(patient.logicalId, TEST_PATIENT_2_ID)
}
database
.getAllLocalChanges()
.map { it }
.none { it.localChange.resourceId in listOf(patient.logicalId, TEST_PATIENT_2_ID) }
)
.isTrue()
}
Expand Down Expand Up @@ -2547,7 +2573,8 @@ class DatabaseImplTest {
)

assertThat(
database.search<Patient>(
database
.search<Patient>(
Search(ResourceType.Patient)
.apply { sort(Patient.BIRTHDATE, Order.DESCENDING) }
.getQuery()
Expand All @@ -2574,7 +2601,8 @@ class DatabaseImplTest {
)

assertThat(
database.search<Patient>(
database
.search<Patient>(
Search(ResourceType.Patient)
.apply { sort(Patient.BIRTHDATE, Order.ASCENDING) }
.getQuery()
Expand Down Expand Up @@ -2832,6 +2860,129 @@ class DatabaseImplTest {
.inOrder()
}

@Test
fun search_patient_with_extension_as_search_param() = runBlocking {
val maidenNameSearchParameter =
SearchParameter().apply {
url = "http:https://example.com/SearchParameter/patient-mothersMaidenName"
addBase("Patient")
name = "mothers-maiden-name"
code = "mothers-maiden-name"
type = Enumerations.SearchParamType.STRING
expression =
"Patient.extension('http:https://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName').value.as(String)"
description = "search on mother's maiden name"
}
val patient =
Patient().apply {
addIdentifier(
Identifier().apply {
system = "https://custom-identifier-namespace"
value = "OfficialIdentifier_DarcySmith_0001"
}
)

addName(
HumanName().apply {
use = HumanName.NameUse.OFFICIAL
family = "Smith"
addGiven("Darcy")
gender = Enumerations.AdministrativeGender.FEMALE
birthDateElement = DateType("1970-01-01")
}
)

addExtension(
Extension().apply {
url = "http:https://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName"
setValue(StringType("Marca"))
}
)
}
// Get rid of the default service and create one with search params
tearDown()
buildFhirService(listOf(maidenNameSearchParameter))
database.insert(patient)

val result =
database.search<Patient>(
Search(ResourceType.Patient)
.apply {
filter(
StringClientParam("mothers-maiden-name"),
{
value = "Marca"
modifier = StringFilterModifier.MATCHES_EXACTLY
}
)
}
.getQuery()
)

assertThat(result.map { it.nameFirstRep.nameAsSingleString }).contains("Darcy Smith")
}

@Test
fun search_patient_with_custom_value_as_search_param() = runBlocking {
val patient =
Patient().apply {
addIdentifier(
Identifier().apply {
system = "https://custom-identifier-namespace"
value = "OfficialIdentifier_DarcySmith_0001"
}
)

addName(
HumanName().apply {
use = HumanName.NameUse.OFFICIAL
family = "Smith"
addGiven("Darcy")
gender = Enumerations.AdministrativeGender.FEMALE
birthDateElement = DateType("1970-01-01")
}
)

addExtension(
Extension().apply {
url = "http:https://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName"
setValue(StringType("Marca"))
}
)
}
val identifierPartialSearchParameter =
SearchParameter().apply {
url = "http:https://example.com/SearchParameter/patient-identifierPartial"
addBase("Patient")
name = "identifierPartial"
code = "identifierPartial"
type = Enumerations.SearchParamType.STRING
expression = "Patient.identifier.value"
description = "Search the identifier"
}
// Get rid of the default service and create one with search params
tearDown()
buildFhirService(listOf(identifierPartialSearchParameter))
database.insert(patient)

val result =
database.search<Patient>(
Search(ResourceType.Patient)
.apply {
filter(
StringClientParam("identifierPartial"),
{
value = "OfficialIdentifier_"
modifier = StringFilterModifier.STARTS_WITH
}
)
}
.getQuery()
)

assertThat(result.map { it.nameFirstRep.nameAsSingleString }).contains("Darcy Smith")
}

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
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Google LLC
* Copyright 2022 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 @@ -26,6 +26,8 @@ import com.google.android.fhir.DatabaseErrorStrategy.UNSPECIFIED
import com.google.android.fhir.db.impl.DatabaseImpl.Companion.DATABASE_PASSPHRASE_NAME
import com.google.android.fhir.db.impl.DatabaseImpl.Companion.ENCRYPTED_DATABASE_NAME
import com.google.android.fhir.db.impl.DatabaseImpl.Companion.UNENCRYPTED_DATABASE_NAME
import com.google.android.fhir.index.ResourceIndexer
import com.google.android.fhir.index.SearchParamDefinitionsProviderImpl
import com.google.android.fhir.search.Order
import com.google.android.fhir.search.Search
import com.google.android.fhir.search.getQuery
Expand All @@ -46,6 +48,7 @@ import org.junit.runner.RunWith
class EncryptedDatabaseErrorTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val parser = FhirContext.forR4().newJsonParser()
private val resourceIndexer = ResourceIndexer(SearchParamDefinitionsProviderImpl())

@After
fun tearDown() {
Expand All @@ -65,7 +68,8 @@ class EncryptedDatabaseErrorTest {
inMemory = false,
enableEncryption = false,
databaseErrorStrategy = UNSPECIFIED
)
),
resourceIndexer
)
.let {
it.insert(TEST_PATIENT_1)
Expand All @@ -81,7 +85,8 @@ class EncryptedDatabaseErrorTest {
inMemory = false,
enableEncryption = true,
databaseErrorStrategy = UNSPECIFIED
)
),
resourceIndexer
)
.let {
it.search<Patient>(
Expand Down Expand Up @@ -110,7 +115,8 @@ class EncryptedDatabaseErrorTest {
inMemory = false,
enableEncryption = true,
databaseErrorStrategy = UNSPECIFIED
)
),
resourceIndexer
)
.let {
it.insert(TEST_PATIENT_1)
Expand All @@ -132,7 +138,8 @@ class EncryptedDatabaseErrorTest {
inMemory = false,
enableEncryption = true,
databaseErrorStrategy = UNSPECIFIED
)
),
resourceIndexer
)
.let {
it.search<Patient>(
Expand Down Expand Up @@ -160,7 +167,8 @@ class EncryptedDatabaseErrorTest {
inMemory = false,
enableEncryption = true,
databaseErrorStrategy = UNSPECIFIED
)
),
resourceIndexer
)
.let {
it.insert(TEST_PATIENT_1)
Expand All @@ -182,7 +190,8 @@ class EncryptedDatabaseErrorTest {
inMemory = false,
enableEncryption = true,
databaseErrorStrategy = RECREATE_AT_OPEN
)
),
resourceIndexer
)
.let {
assertThat(
Expand Down Expand Up @@ -213,7 +222,8 @@ class EncryptedDatabaseErrorTest {
inMemory = false,
enableEncryption = true,
databaseErrorStrategy = UNSPECIFIED
)
),
resourceIndexer
)
.let {
it.insert(TEST_PATIENT_1)
Expand All @@ -229,7 +239,8 @@ class EncryptedDatabaseErrorTest {
inMemory = false,
enableEncryption = false,
databaseErrorStrategy = UNSPECIFIED
)
),
resourceIndexer
)
.let {
assertThat(
Expand Down

0 comments on commit 5f17993

Please sign in to comment.