commonMain.com.caesarealabs.searchit.impl.SearchitQuery.kt Maven / Gradle / Ivy
The newest version!
package com.caesarealabs.searchit.impl
import com.caesarealabs.searchit.*
internal suspend fun Searchit.search(query: SearchitQuery): List {
// For performance, do key:value searches on the database level. This can be greatly optimized with indices.
val topLevelKeyValueFilters = query.filters.filterIsInstance()
// Only query with queryableProperties, so that the other keys may be queries with DataLens.hasAdditionalKeyValue
.filter { it.key in lens.directKeys }
.associate { it.key to it.value }
val inMemoryResults = database.query(query.timeRange, topLevelKeyValueFilters)
return inMemoryResults.searchInMemory(query.filters, lens)
}
internal fun List.searchInMemory(filters: List, dataLens: DataLens): List {
return filter {
for (filter in filters) {
if (!filter.toPredicate(dataLens)(it)) return@filter false
}
true
}
}
/**
* At the end of the day, every log filter represents a (T) -> Boolean - either accept this item or don't.
* Each filter just has different conditions.
*/
private fun SearchitFilter.toPredicate(dataLens: DataLens): (T) -> Boolean = when (this) {
is SearchitFilter.And -> {
val firstCondition = first.toPredicate(dataLens)
val secondCondition = second.toPredicate(dataLens)
({ firstCondition(it) && secondCondition(it) })
}
is SearchitFilter.Or -> {
val firstCondition = first.toPredicate(dataLens)
val secondCondition = second.toPredicate(dataLens)
({ firstCondition(it) || secondCondition(it) })
}
is SearchitFilter.Not -> {
val condition = filter.toPredicate(dataLens)
({ !condition(it) })
}
is SearchitFilter.KeyValue -> ({
// queryableProperties are queried on the database level
key in dataLens.directKeys || dataLens.hasKeyValue(it, key, value)
})
is SearchitFilter.Special -> ({ filter(it) })
is SearchitFilter.Text -> ({ dataLens.containsText(it, text) })
SearchitFilter.None -> ({ true })
}
// public for tests
internal sealed interface SearchitFilter {
data class Not(val filter: SearchitFilter) : SearchitFilter
data class Or(val first: SearchitFilter, val second: SearchitFilter) : SearchitFilter
data class And(val first: SearchitFilter, val second: SearchitFilter) : SearchitFilter
data class KeyValue(val key: String, val value: String) : SearchitFilter
data class Text(val text: String) : SearchitFilter
data class Special(val filter: Filter) : SearchitFilter
object None : SearchitFilter
}
/**
* How a search works:
* - First, we use objectbox directly to filter by start and end date. (This is why start/end time is seperate here)
* - Then, we filter in-memory for the rest of the filters. Since start/end date should filter most things, this is not too bad.
* This could be optimized in the future to use indices for everything. It would require storing the logs differently though.
*
* In [filters] we use a list of AND'd filters instead of one big recursive LogFilter.And for easier debugging.
*/
internal data class SearchitQuery(val timeRange: TimeRange, val filters: List)