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

Support Contains for string search #426

Merged
merged 14 commits into from
May 14, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.google.android.fhir.logicalId
import com.google.android.fhir.resource.TestingUtils
import com.google.android.fhir.search.Order
import com.google.android.fhir.search.Search
import com.google.android.fhir.search.StringFilterModifier
import com.google.android.fhir.search.getQuery
import com.google.android.fhir.sync.FhirDataSource
import com.google.common.truth.Truth.assertThat
Expand Down Expand Up @@ -371,6 +372,139 @@ class DatabaseImplTest {
)
}

@Test
fun search_string_default() {
val patient =
Patient().apply {
id = "test_1"
addName(HumanName().addGiven("Evelyn"))
}
val res = runBlocking {
database.insert(patient)
database.search<Patient>(
Search(ResourceType.Patient).apply { filter(Patient.GIVEN) { value = "eve" } }.getQuery()
)
}

assertThat(res).hasSize(1)
epicadk marked this conversation as resolved.
Show resolved Hide resolved
assertThat(res[0].id).isEqualTo("Patient/${patient.id}")
}

@Test
fun search_string_default_no_match() {
val patient =
Patient().apply {
id = "test_1"
addName(HumanName().addGiven("Severine"))
}
val res = runBlocking {
database.insert(patient)
database.search<Patient>(
Search(ResourceType.Patient).apply { filter(Patient.GIVEN) { value = "eve" } }.getQuery()
)
}

assertThat(res).hasSize(0)
}

@Test
fun search_string_exact() {
val patient =
Patient().apply {
id = "test_1"
addName(HumanName().addGiven("Eve"))
}
val res = runBlocking {
database.insert(patient)
database.search<Patient>(
Search(ResourceType.Patient)
.apply {
filter(Patient.GIVEN) {
value = "Eve"
modifier = StringFilterModifier.MATCHES_EXACTLY
}
}
.getQuery()
)
}

assertThat(res).hasSize(1)
assertThat(res[0].id).isEqualTo("Patient/${patient.id}")
}
@Test
fun search_string_exact_no_match() {
val patient =
Patient().apply {
id = "test_1"
addName(HumanName().addGiven("EVE"))
}
val res = runBlocking {
database.insert(patient)
database.search<Patient>(
Search(ResourceType.Patient)
.apply {
filter(Patient.GIVEN) {
value = "Eve"
modifier = StringFilterModifier.MATCHES_EXACTLY
}
}
.getQuery()
)
}

assertThat(res).hasSize(0)
}

@Test
fun search_string_contains() {
val patient =
Patient().apply {
id = "test_1"
addName(HumanName().addGiven("Severine"))
}

val res = runBlocking {
database.insert(patient)
database.search<Patient>(
Search(ResourceType.Patient)
.apply {
filter(Patient.GIVEN) {
value = "Eve"
modifier = StringFilterModifier.CONTAINS
}
}
.getQuery()
)
}

assertThat(res).hasSize(1)
assertThat(res[0].id).isEqualTo("Patient/${patient.id}")
}

@Test
fun search_string_contains_no_match() {
val patient =
Patient().apply {
id = "test_1"
addName(HumanName().addGiven("John"))
}
val res = runBlocking {
database.insert(patient)
database.search<Patient>(
Search(ResourceType.Patient)
.apply {
filter(Patient.GIVEN) {
value = "eve"
modifier = StringFilterModifier.CONTAINS
}
}
.getQuery()
)
}

assertThat(res).hasSize(0)
}

private companion object {
const val TEST_PATIENT_1_ID = "test_patient_1"
val TEST_PATIENT_1 = Patient()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,17 @@ fun Search.getQuery(): SearchQuery {
}

fun StringFilter.query(type: ResourceType): SearchQuery {

val condition =
when (modifier) {
StringFilterModifier.STARTS_WITH -> "LIKE ? || '%' COLLATE NOCASE"
StringFilterModifier.MATCHES_EXACTLY -> "= ?"
StringFilterModifier.CONTAINS -> "LIKE '%' || ? || '%' COLLATE NOCASE"
}
return SearchQuery(
"""
SELECT resourceId FROM StringIndexEntity
WHERE resourceType = ? AND index_name = ? AND index_value = ? COLLATE NOCASE
WHERE resourceType = ? AND index_name = ? AND index_value $condition
""",
listOf(type.name, parameter.paramName, value!!)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import ca.uhn.fhir.rest.gclient.IParam
import ca.uhn.fhir.rest.gclient.NumberClientParam
import ca.uhn.fhir.rest.gclient.ReferenceClientParam
import ca.uhn.fhir.rest.gclient.StringClientParam
import ca.uhn.fhir.rest.param.ParamPrefixEnum
import org.hl7.fhir.r4.model.ResourceType

@SearchDslMarker
Expand Down Expand Up @@ -56,7 +55,7 @@ data class Search(val type: ResourceType, var count: Int? = null, var from: Int?
@SearchDslMarker
data class StringFilter(
val parameter: StringClientParam,
var prefix: ParamPrefixEnum? = null,
var modifier: StringFilterModifier = StringFilterModifier.STARTS_WITH,
var value: String? = null
)

Expand All @@ -67,3 +66,9 @@ enum class Order {
ASCENDING,
DESCENDING
}

enum class StringFilterModifier {
STARTS_WITH,
MATCHES_EXACTLY,
CONTAINS
}
113 changes: 98 additions & 15 deletions engine/src/test/java/com/google/android/fhir/search/SearchTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package com.google.android.fhir.search

import android.os.Build
import ca.uhn.fhir.rest.param.ParamPrefixEnum
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import org.hl7.fhir.r4.model.Patient
Expand Down Expand Up @@ -47,6 +46,100 @@ class SearchTest {
assertThat(query.args).isEqualTo(listOf(ResourceType.Patient.name))
}

@Test
fun search_string_default() {
val query =
Search(ResourceType.Patient)
.apply { filter(Patient.ADDRESS) { value = "someValue" } }
.getQuery()

assertThat(query.query)
.isEqualTo(
"""
SELECT a.serializedResource
FROM ResourceEntity a
WHERE a.resourceType = ?
AND a.resourceId IN (
SELECT resourceId FROM StringIndexEntity
WHERE resourceType = ? AND index_name = ? AND index_value LIKE ? || '%' COLLATE NOCASE
)
""".trimIndent()
)
assertThat(query.args)
.containsExactly(
ResourceType.Patient.name,
ResourceType.Patient.name,
Patient.ADDRESS.paramName,
"someValue"
)
}

@Test
fun search_string_exact() {
val query =
Search(ResourceType.Patient)
.apply {
filter(Patient.ADDRESS) {
modifier = StringFilterModifier.MATCHES_EXACTLY
value = "someValue"
}
}
.getQuery()

assertThat(query.query)
.isEqualTo(
"""
SELECT a.serializedResource
FROM ResourceEntity a
WHERE a.resourceType = ?
AND a.resourceId IN (
SELECT resourceId FROM StringIndexEntity
WHERE resourceType = ? AND index_name = ? AND index_value = ?
)
""".trimIndent()
)
assertThat(query.args)
.containsExactly(
ResourceType.Patient.name,
ResourceType.Patient.name,
Patient.ADDRESS.paramName,
"someValue"
)
}

@Test
fun search_string_contains() {
val query =
Search(ResourceType.Patient)
.apply {
filter(Patient.ADDRESS) {
modifier = StringFilterModifier.CONTAINS
value = "someValue"
}
}
.getQuery()

assertThat(query.query)
.isEqualTo(
"""
SELECT a.serializedResource
FROM ResourceEntity a
WHERE a.resourceType = ?
AND a.resourceId IN (
SELECT resourceId FROM StringIndexEntity
WHERE resourceType = ? AND index_name = ? AND index_value LIKE '%' || ? || '%' COLLATE NOCASE
)
""".trimIndent()
)
assertThat(query.args)
.containsExactly(
ResourceType.Patient.name,
ResourceType.Patient.name,
Patient.ADDRESS.paramName,
"someValue"
)
}

@Test
fun search_size() {
val query = Search(ResourceType.Patient).apply { count = 10 }.getQuery()
Expand Down Expand Up @@ -88,14 +181,7 @@ class SearchTest {
@Test
fun search_filter() {
val query =
Search(ResourceType.Patient)
.apply {
filter(Patient.FAMILY) {
prefix = ParamPrefixEnum.EQUAL
value = "Jones"
}
}
.getQuery()
Search(ResourceType.Patient).apply { filter(Patient.FAMILY) { value = "Jones" } }.getQuery()

assertThat(query.query)
.isEqualTo(
Expand All @@ -105,7 +191,7 @@ class SearchTest {
WHERE a.resourceType = ?
AND a.resourceId IN (
SELECT resourceId FROM StringIndexEntity
WHERE resourceType = ? AND index_name = ? AND index_value = ? COLLATE NOCASE
WHERE resourceType = ? AND index_name = ? AND index_value LIKE ? || '%' COLLATE NOCASE
)
""".trimIndent()
)
Expand Down Expand Up @@ -183,10 +269,7 @@ class SearchTest {
val query =
Search(ResourceType.Patient)
.apply {
filter(Patient.FAMILY) {
prefix = ParamPrefixEnum.EQUAL
value = "Jones"
}
filter(Patient.FAMILY) { value = "Jones" }
sort(Patient.GIVEN, Order.ASCENDING)
count = 10
from = 20
Expand All @@ -203,7 +286,7 @@ class SearchTest {
WHERE a.resourceType = ?
AND a.resourceId IN (
SELECT resourceId FROM StringIndexEntity
WHERE resourceType = ? AND index_name = ? AND index_value = ? COLLATE NOCASE
WHERE resourceType = ? AND index_name = ? AND index_value LIKE ? || '%' COLLATE NOCASE
)
ORDER BY b.index_value ASC
LIMIT ? OFFSET ?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.liveData
import ca.uhn.fhir.rest.param.ParamPrefixEnum
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.reference.data.SamplePatients
import com.google.android.fhir.search.Order
import com.google.android.fhir.search.StringFilterModifier
import com.google.android.fhir.search.search
import org.hl7.fhir.r4.model.Patient

Expand All @@ -43,7 +43,7 @@ class PatientListViewModel(application: Application, private val fhirEngine: Fhi
val searchResults: List<Patient> =
fhirEngine.search {
filter(Patient.ADDRESS_CITY) {
prefix = ParamPrefixEnum.EQUAL
modifier = StringFilterModifier.MATCHES_EXACTLY
value = "NAIROBI"
}
sort(Patient.GIVEN, Order.ASCENDING)
Expand Down