All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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)





© 2015 - 2024 Weber Informatics LLC | Privacy Policy