kt-search

Multi platform kotlin client for Elasticsearch & Opensearch with easily extendable Kotlin DSLs for queries, mappings, bulk, and more.


Project maintained by jillesvangurp Hosted on GitHub Pages — Theme by mattgraham

Reusing your Query logic

KT Search Manual Previous: Highlighting Next: Deleting by query
Github © Jilles van Gurp  

The Search DSL allows you to construct queries via helper extension functions on SearchDSL and the QueryClauses interface. This makes it easy to use with the search functions in the client or IndexRepository.

Here’s a simple example of a query that looks for the “best thing ever” suitable for work. We’ll build out this example to show you how to

client.search(indexName) {
  query = bool {
    filter(term(TestDoc::tags, "work"))
    must(matchPhrase(TestDoc::name, "best thing ever"))
  }
}

In this example, bool, and match are all extension functions on QueryClauses and the receiver block has a this of type SearchDSL, which implements QueryClauses.

Adding some logic

Because it is all kotlin, you can programmatically construct complex queries, use conditional logic, maybe look up a few things and generate clauses for that, etc. All while keeping things type safe.

Let’s make our example a bit more interesting by taking time into account.

val now = Clock.System.now()

client.search(indexName) {
  query = bool {
    val hour = now.toLocalDateTime(TimeZone.UTC).hour
    if (hour in 9..17) {
      filter(
        term(TestDoc::tags, "work")
      )
    } else {
      filter(
        term(TestDoc::tags, "leisure")
      )
    }
    must(matchPhrase(TestDoc::name, "best thing ever"))
  }
}

This simple example creates different queries depending on the time.

After all, what’s the best thing ever probably depends on some context. Which would include the time of day.

Writing your own extension function

Conditional logic like above can get complicated pretty quickly. To keep that under control, you will want to use all the usual strategies such as extracting functions, using object oriented designs, or doing things with extension functions, delegation, etc.

Note, that the example above uses UTC. Obviously we’d want to take things like Timezones into account as well. And maybe the user has a calendar and a busy schedule. There are all sorts of things to consider!

The point is that queries can get complicated quite quickly and you need good mechanisms to keep things clean and structured. Kt-search provides you all the tools you need to stay on top of this.

fun QueryClauses.timeTagClause() = term(TestDoc::tags,
  if(Clock.System.now().toLocalDateTime(TimeZone.UTC).hour in 9..17) {
    "work"
  } else {
    "leisure"
  })

client.search(indexName) {
  bool {
    filter(timeTagClause())
    must(matchPhrase(TestDoc::name, "best thing ever"))
  }
}

client.search(indexName) {
  bool {
    filter(timeTagClause())
    must(matchPhrase(TestDoc::name, "something I need"))
  }
}

Here’s an improved version of the query. We extracted the logic that creates the term clause and we can now easily add it to different queries.

Helper functions

The above example is nice but it is not always practical to rely on having a receiver object.

class MyFancyQueryBuilder {
  fun timeTagClause() = constructQueryClause {
    term(
      TestDoc::tags,
      if (Clock.System.now().toLocalDateTime(TimeZone.UTC).hour in 9..17) {
        "work"
      } else {
        "leisure"
      }
    )
  }
}

val qb = MyFancyQueryBuilder()

client.search(indexName) {
  bool {
    filter(qb.timeTagClause())
    must(matchPhrase(TestDoc::name, "best thing ever"))
  }
}

Here we introduce a helper class. You could imagine adding all sorts of logic and helper classes.

Of course using a class creates the problem that using that it complicates using extension functions.

The constructQueryClause helper function provides a solution to that problem. It allows you to create functions that take a block that expects a QueryClauses object and returns an ESQuery. And it then creates an anonymous QueryClauses object for you uses that to call your block.


KT Search Manual Previous: Highlighting Next: Deleting by query
Github © Jilles van Gurp