Skip to content

Optional criteria

When developing user interfaces, we often expose ways to query data that the user can control. In some cases, we know exactly what the user is searching for and can thus write a dedicated query. In some other cases, we provide multiple filters to the user that they can enable or disable.

These optional criteria make queries harder to write, and more importantly harder to read. Since they appear often and can grow quite complex, KtMongo provides operators specifically for this pattern.

Example

As an example, we will query a list of songs based on its author and release date. For now, we assume all criteria are mandatory, and thus we receive a request that looks like:

class SongCriteria(
    val authorName: String,
    val releasedAfter: Instant,
    val releasedBefore: Instant,
)

The request can be written:

val criteria: SongCriteria = getCriteriaFromUser()

songs.find {
    Song::author / Author::name eq criteria.authorName
    Song::releaseDate gt criteria.releasedAfter
    Song::releaseDate lt criteria.releasedBefore
}

However, in the real world, we may be interested in allowing users to search for songs without specifying a release date range. We'd rather make the last two criteria optional:

class SongCriteria(
    val authorName: String,
    val releasedAfter: Instant? = null,
    val releasedBefore: Instant? = null,
)

Taking advantage of the DSL

Since all KtMongo operators are written using DSLs, we can use any constructs from Kotlin directly within requests:

val criteria: SongCriteria = getCriteriaFromUser()

songs.find {
    Song::author / Author::name eq criteria.authorName

    if (criteria.releasedAfter != null)
        Song::releaseDate gt criteria.releasedAfter

    if (criteria.releasedBefore != null)
        Song::releaseDate lt criteria.releasedBefore
}

This is simple to read, but is a bit verbose especially when there are many such criteria.

Optional filter operators

KtMongo provides variants of filter operators that do nothing if their argument is null. Using them, we can rewrite the request as:

val criteria: SongCriteria = getCriteriaFromUser()

songs.find {
    Song::author / Author::name eq criteria.authorName
    Song::releaseDate gtNotNull criteria.releasedAfter
    Song::releaseDate ltNotNull criteria.releasedBefore
}