git.iko.soy/parseable-android
on master help sign in
Fix search failing on streams with non-text columns (#31)

The search ILIKE clause was applied directly to columns of all types, causing SQL errors on numeric/boolean/date columns. Wrap column references in CAST(... AS VARCHAR) so the text pattern match works regardless of the underlying data type. Also fix two related issues: - Missing parentheses around the search OR clause in pollNewLogs(), which broke operator precedence when combined with filter AND clauses during live streaming. - applyBuilderRules() called addFilter() per rule (each triggering a refresh), causing N+1 redundant API calls. Extract addFilterInternal() to batch state updates without intermediate refreshes. https://claude.ai/code/session_01Unnt5vuNVWEVJGTAJ7kbpj Co-authored-by: Claude <noreply@anthropic.com>

commit0bc5ab9d10ba971aad6f561bbebb70257bfcbafa authoriko <mail@iko.soy> dateSat 21 Feb 2026, 16:09 (7 months ago) parente8385c8
2 files changed+11−7
app/src/main/java/com/parseable/android/ui/screens/logviewer/LogViewerViewModel.kt+8−4 app/src/test/java/com/parseable/android/LogViewerViewModelTest.kt+3−3
app/src/main/java/com/parseable/android/ui/screens/logviewer/LogViewerViewModel.kt+8−4
@@ -188,6 +188,11 @@ class LogViewerViewModel @Inject constructor(
188188 }
189189
190190 fun addFilter(column: String, operator: String, value: String) {
191+ addFilterInternal(column, operator, value)
192+ refresh()
193+ }
194+
195+ private fun addFilterInternal(column: String, operator: String, value: String) {
191196 if (operator !in ALLOWED_OPERATORS) return
192197 val safeColumn = escapeIdentifier(column)
193198 val safeValue = escapeSql(value)
@@ -208,7 +213,6 @@ class LogViewerViewModel @Inject constructor(
208213 ),
209214 )
210215 }
211− refresh()
212216 }
213217
214218 fun removeFilter(display: String) {
@@ -280,7 +284,7 @@ class LogViewerViewModel @Inject constructor(
280284 if (searchableColumns.isEmpty()) return null
281285 val safeSearch = escapeSql(searchQuery)
282286 return searchableColumns.joinToString(" OR ") {
283− "\"${escapeIdentifier(it)}\" ILIKE '%$safeSearch%'"
287+ "CAST(\"${escapeIdentifier(it)}\" AS VARCHAR) ILIKE '%$safeSearch%'"
284288 }
285289 }
286290
@@ -439,7 +443,7 @@ class LogViewerViewModel @Inject constructor(
439443 if (current.filters.searchQuery.isNotBlank()) {
440444 val searchClause = buildSearchClause(current.searchableColumns, current.filters.searchQuery)
441445 if (searchClause != null) {
442− clauses.add(searchClause)
446+ clauses.add("($searchClause)")
443447 }
444448 }
445449
@@ -640,7 +644,7 @@ class LogViewerViewModel @Inject constructor(
640644 for (group in builder.rules) {
641645 for (rule in group.rules) {
642646 if (rule.field.isNotBlank()) {
643− addFilter(rule.field, rule.operator, rule.value)
647+ addFilterInternal(rule.field, rule.operator, rule.value)
644648 }
645649 }
646650 }
app/src/test/java/com/parseable/android/LogViewerViewModelTest.kt+3−3
@@ -164,9 +164,9 @@ class LogViewerViewModelTest {
164164 startTime = any(),
165165 endTime = any(),
166166 filterSql = match {
167− it.contains("\"message\" ILIKE '%error%'") &&
168− it.contains("\"level\" ILIKE '%error%'") &&
169− !it.contains("p_timestamp ILIKE") // Internal columns excluded
167+ it.contains("CAST(\"message\" AS VARCHAR) ILIKE '%error%'") &&
168+ it.contains("CAST(\"level\" AS VARCHAR) ILIKE '%error%'") &&
169+ !it.contains("p_timestamp") // Internal columns excluded
170170 },
171171 limit = any(),
172172 )