Skip to content

Commit 2642304

Browse files
committed
[ANDROSDK-2309-APPLY-TO-FILTERS] fix SQL condition building
1 parent 74c14d5 commit 2642304

5 files changed

Lines changed: 103 additions & 20 deletions

File tree

core/src/main/java/org/hisp/dhis/android/core/arch/repositories/scope/internal/FilterItemOperator.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@ enum class FilterItemOperator(val sqlOperator: String, val apiOperator: String,
4040
SW("LIKE", "sw", "SW"),
4141
EW("LIKE", "ew", "EW"),
4242
VOID("", "", ""),
43-
NULL_OR_BLANK("IS NULL OR =''", "null_or_blank", "NULL_OR_BLANK"),
44-
NOT_NULL_AND_NOT_BLANK("IS NOT NULL AND !=''", "not_null_and_not_blank", "NOT_NULL_AND_NOT_BLANK"),
43+
NULL_OR_BLANK("", "null_or_blank", "NULL_OR_BLANK"),
44+
NOT_NULL_AND_NOT_BLANK("", "not_null_and_not_blank", "NOT_NULL_AND_NOT_BLANK"),
45+
;
46+
47+
/**
48+
* Builds the full SQL condition for this operator. NULL_OR_BLANK and NOT_NULL_AND_NOT_BLANK cannot be expressed
49+
* as "column sqlOperator value" because they need the column name in both sides of the OR/AND condition.
50+
*/
51+
internal fun getSqlCondition(column: String, valueStr: String? = null): String {
52+
return when (this) {
53+
NULL_OR_BLANK -> "($column IS NULL OR $column = '')"
54+
NOT_NULL_AND_NOT_BLANK -> "($column IS NOT NULL AND $column != '')"
55+
else -> "$column $sqlOperator $valueStr"
56+
}
57+
}
4558
}

core/src/main/java/org/hisp/dhis/android/core/arch/repositories/scope/internal/WhereClauseFromScopeBuilder.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ internal class WhereClauseFromScopeBuilder(private val builder: WhereClauseBuild
3636
return "1"
3737
}
3838
for (item in scope.filters()) {
39-
builder.appendKeyOperatorValue(item.key(), item.operator().sqlOperator, item.value() ?: "")
39+
when (item.operator()) {
40+
FilterItemOperator.NULL_OR_BLANK, FilterItemOperator.NOT_NULL_AND_NOT_BLANK ->
41+
builder.appendComplexQuery(item.operator().getSqlCondition(item.key()))
42+
43+
else -> builder.appendKeyOperatorValue(item.key(), item.operator().sqlOperator, item.value() ?: "")
44+
}
4045
}
4146
for (item in scope.complexFilters()) {
4247
builder.appendComplexQuery(item.whereQuery())

core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceLocalQueryHelper.kt

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -343,24 +343,21 @@ internal class TrackedEntityInstanceLocalQueryHelper(
343343
for (token in tokens) {
344344
val valueStr =
345345
if (query.operator() == FilterItemOperator.LIKE) {
346-
"%${escapeQuotes(token)}%"
347-
} else if (query.operator() == FilterItemOperator.NULL_OR_BLANK ||
348-
query.operator() == FilterItemOperator.NOT_NULL_AND_NOT_BLANK
349-
) {
350-
token
346+
"'%${escapeQuotes(token)}%'"
351347
} else {
352-
escapeQuotes(token)
348+
"'${escapeQuotes(token)}'"
353349
}
354350

355351
val sub = String.format(
356-
"SELECT 1 FROM %s %s WHERE %s = %s AND %s %s '%s'",
352+
"SELECT 1 FROM %s %s WHERE %s = %s AND %s",
357353
TrackedEntityAttributeValueTableInfo.TABLE_INFO.name(),
358354
teavAlias,
359355
dot(teavAlias, trackedEntityInstance),
360356
dot(teiAlias, IdentifiableColumns.UID),
361-
dot(teavAlias, TrackedEntityAttributeValueTableInfo.Columns.VALUE),
362-
query.operator().sqlOperator,
363-
valueStr,
357+
query.operator().getSqlCondition(
358+
dot(teavAlias, TrackedEntityAttributeValueTableInfo.Columns.VALUE),
359+
valueStr,
360+
),
364361
)
365362

366363
where.appendExistsSubQuery(sub)
@@ -371,13 +368,14 @@ internal class TrackedEntityInstanceLocalQueryHelper(
371368
private fun appendFiltersWhere(where: WhereClauseBuilder, scope: TrackedEntityInstanceQueryRepositoryScope) {
372369
for (item in scope.filter()) {
373370
val sub = String.format(
374-
"SELECT 1 FROM %s %s WHERE %s = %s AND %s = '%s' AND %s %s %s",
371+
"SELECT 1 FROM %s %s WHERE %s = %s AND %s = '%s' AND %s",
375372
TrackedEntityAttributeValueTableInfo.TABLE_INFO.name(), teavAlias,
376373
dot(teavAlias, trackedEntityInstance), dot(teiAlias, IdentifiableColumns.UID),
377374
dot(teavAlias, trackedEntityAttribute), escapeQuotes(item.key()),
378-
dot(teavAlias, TrackedEntityAttributeValueTableInfo.Columns.VALUE),
379-
item.operator().sqlOperator,
380-
getFilterItemValueStr(item),
375+
item.operator().getSqlCondition(
376+
dot(teavAlias, TrackedEntityAttributeValueTableInfo.Columns.VALUE),
377+
getFilterItemValueStr(item),
378+
),
381379
)
382380

383381
where.appendExistsSubQuery(sub)
@@ -574,9 +572,12 @@ internal class TrackedEntityInstanceLocalQueryHelper(
574572
"'${escapeQuotes(key)}' " +
575573

576574
items.joinToString("") { item ->
577-
"AND ${dot(tedvAlias, TrackedEntityDataValueTableInfo.Columns.VALUE)} " +
578-
"${item.operator().sqlOperator} " +
579-
"${getFilterItemValueStr(item)} "
575+
"AND ${
576+
item.operator().getSqlCondition(
577+
dot(tedvAlias, TrackedEntityDataValueTableInfo.Columns.VALUE),
578+
getFilterItemValueStr(item)
579+
)
580+
} "
580581
}
581582

582583
where.appendExistsSubQuery(sub)

core/src/test/java/org/hisp/dhis/android/core/arch/repositories/scope/WhereClauseFromScopeBuilderShould.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ class WhereClauseFromScopeBuilderShould {
5050
RepositoryScopeFilterItem.builder().key("k1").operator(FilterItemOperator.EQ).value("v1").build()
5151
private val likeItem =
5252
RepositoryScopeFilterItem.builder().key("k2").operator(FilterItemOperator.LIKE).value("v2").build()
53+
private val nullOrBlankItem =
54+
RepositoryScopeFilterItem.builder().key("k3").operator(FilterItemOperator.NULL_OR_BLANK).build()
55+
private val notNullAndNotBlankItem =
56+
RepositoryScopeFilterItem.builder().key("k4").operator(FilterItemOperator.NOT_NULL_AND_NOT_BLANK).build()
5357

5458
@Test
5559
fun build_where_statement_for_equals_key_value() {
@@ -90,6 +94,28 @@ class WhereClauseFromScopeBuilderShould {
9094
verifyNoMoreInteractions(builder)
9195
}
9296

97+
@Test
98+
fun build_where_statement_for_null_or_blank_key() {
99+
val scopeBuilder = WhereClauseFromScopeBuilder(builder)
100+
whenever(builder.build()).doReturn("1")
101+
102+
scopeBuilder.getWhereClause(scopeForItems(listOf(nullOrBlankItem)))
103+
verify(builder).appendComplexQuery("(k3 IS NULL OR k3 = '')")
104+
verify(builder).build()
105+
verifyNoMoreInteractions(builder)
106+
}
107+
108+
@Test
109+
fun build_where_statement_for_not_null_and_not_blank_key() {
110+
val scopeBuilder = WhereClauseFromScopeBuilder(builder)
111+
whenever(builder.build()).doReturn("1")
112+
113+
scopeBuilder.getWhereClause(scopeForItems(listOf(notNullAndNotBlankItem)))
114+
verify(builder).appendComplexQuery("(k4 IS NOT NULL AND k4 != '')")
115+
verify(builder).build()
116+
verifyNoMoreInteractions(builder)
117+
}
118+
93119
@Test
94120
fun build_where_statement_when_no_filters_on_empty_builder() {
95121
val scopeBuilder = WhereClauseFromScopeBuilder(builder)

core/src/test/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceLocalQueryHelperShould.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,4 +230,42 @@ class TrackedEntityInstanceLocalQueryHelperShould {
230230

231231
assertThat(sqlQuery).contains("value IN ('element1','element2')")
232232
}
233+
234+
@Test
235+
fun build_sql_query_with_null_or_blank_attribute_filter() {
236+
val scope = queryBuilder
237+
.program(programUid)
238+
.filter(
239+
listOf(
240+
RepositoryScopeFilterItem.builder()
241+
.key("key")
242+
.operator(FilterItemOperator.NULL_OR_BLANK)
243+
.build(),
244+
),
245+
)
246+
.build()
247+
248+
val sqlQuery = localQueryHelper.getSqlQuery(scope, emptySet(), 50)
249+
250+
assertThat(sqlQuery).contains("(teav.value IS NULL OR teav.value = '')")
251+
}
252+
253+
@Test
254+
fun build_sql_query_with_not_null_and_not_blank_data_value() {
255+
val scope = queryBuilder
256+
.program(programUid)
257+
.dataValue(
258+
listOf(
259+
RepositoryScopeFilterItem.builder()
260+
.key("dataElement")
261+
.operator(FilterItemOperator.NOT_NULL_AND_NOT_BLANK)
262+
.build(),
263+
),
264+
)
265+
.build()
266+
267+
val sqlQuery = localQueryHelper.getSqlQuery(scope, emptySet(), 50)
268+
269+
assertThat(sqlQuery).contains("(tedv.value IS NOT NULL AND tedv.value != '')")
270+
}
233271
}

0 commit comments

Comments
 (0)