orderBy -> sort

The orderBy clause translates to the ElasticSearch sort.

firstRows/maxRows -> from/size

The firstRows and maxRows translate to the ElasticSearch from and size respectively.

select/fetch -> fields/include

The select and fetch clauses of an ORM query translate to the ElasticSearch fields and _source include. For background reading there is managing elasticsearch fields when searching.

Ebean will translate a fetch() clause into a _source include if the fetch is a * "fetch all properties for the path" or if the path is a OneToMany or ManyToMany.

The expectation is that there will be some specific queries we want to optimise (to support Autocomplete UI etc) and for those we can store the fields we want to fetch (so that ElasticSearch does not need to read them out of the _source) and then use fields to specifically only fetch those fields. An expected use case would be to support autocomplete for 'customer name' or 'product name' etc where we really only need the id and name fields.

If no select() or fetch() is specified then no fields or _source section is included in the query and ElasticSearch will return the _source.

where -> filter context

Expressions added to where becomes expression in the ElasticSearch filter context.

Expression in the filter context are not scored and do not take part in relevance ordering. The benefit of expressions in the filter context is that they can be cached by ElasticSearch for performance.

By default where() expressions are joined by MUST

When multiple expressions are added to where() they are by default added using MUST and this is consistent with the 'object relational' default of AND.

Example: filter context

PagedList<Order> orders =
  Order.find
    .where()
      // default to MUST for where()
      .customer.name.istartsWith("Rob")
      .status.eq(Order.Status.COMPLETE)
    .setMaxRows(50)
    .setUseDocStore(true)
    .findPagedList();
{
  "size": 50,
  "query": {
    "filtered": {
      "filter": {
        "bool": {
          "must": [
            { "prefix": { "customer.name": "rob" } },
            { "term": { "status": "COMPLETE" } }
          ]
        }
      }
    }
  }
}

text -> query context

Expressions added to text become expressions in the ElasticSearch query context. These expressions are scored and are used to determine the relevance ordering of documents.

If expressions are added text() then the query is automatically deemed to be a document store query.

By default text() expressions are joined by SHOULD

When multiple expressions are added to text() they are by default added using SHOULD and this is consistent with the 'object relational' default of OR.

Example: query context

PagedList<Order> orders =
  Order.find
    .text()
      // implicitly sets setUseDocStore(true)
      // goes to ElasticSearch 'query context'
      // defaults to SHOULD for text()
      .customer.name.match("Rob")
      .status.eq(Order.Status.COMPLETE)
    .setMaxRows(50)
    .findPagedList();
{
  "size": 50,
  "query": {
    "bool": {
      "should": [
        { "match": { "customer.name": "Rob" } },
        { "term": { "status": "COMPLETE" } }
      ]
    }
  }
}

Example: query context and filter context

A "Full text" query can have both the query context and filter context like the example query below.

List<Order> orders = Order.find
    .text() // 'query context'
      .must()
        .customer.name.match("Rob")
        .customer.name.match("Bygr")
        .endJunction()
      .mustNot()
        .customer.status.eq(Customer.Status.ACTIVE)
    .where() // 'filter context'
      .should()
        .status.eq(Order.Status.COMPLETE)
        .status.isNotNull()
    .findList();
{
  "query": {
    "bool": {
      "must": [
        { "match": { "customer.name": "Rob" } },
        { "match": { "customer.name": "Bygr" } }
      ],
      "must_not": [
        { "term": { "customer.status": "ACTIVE" } }
      ]
    }
  },
  "filter": {
    "bool": {
      "should": [
        { "term": { "status": "COMPLETE" } },
        { "exists": { "field": "status" } }
      ]
    }
  }
}

Explicit useDocStore(true)

If useDocStore(true) is set then the query will execute against ElasticSearch.

Implied useDocStore(true)

A query is implicitly set as a doc store query when:

  • text() is used
  • Text junctions are used - must(), mustNot(), should()
  • A "Full text" expression is used - match(), multiMatch(), textSimple(), textQueryString(), textCommonTerms()

Must, must not, should

MUST, MUST NOT and SHOULD are junction expressions that are using in text searches and map to AND, NOT and OR respectively.

We can not use must(), mustNot() or should() in normal ORM queries and as soon as we use one of these 'text search junction expressions' the query is automatically considered a document store query and will hit ElasticSearch.

Product
 .where()
   // we used must() so automatically
   // becomes a doc store query
   .must()
     .sku.equalTo("C002")
     .findList();

We use must(), mustNot() and should() to join expressions.

List<Customer> customers =
  server.find(Customer.class)
    .text()
    .must()
      .match("name", "Rob")
      .match("smallNote", "interesting")
    .findList();
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "Rob" } },
        { "match": { "smallNote": "interesting" } }
      ]
    }
  }
}

We use endJunction() to end the 'current junction' so that we can start another one.

List<Order> orders = Order.find
    .text() // 'query context'
      .must()
        .customer.name.match("Rob")
        .customer.name.match("Bygr")
        .endJunction()
      .mustNot()
        .customer.status.eq(Customer.Status.ACTIVE)
    .where() // 'filter context'
      .should()
        .status.eq(Order.Status.COMPLETE)
        .status.isNotNull()
    .findList();
{
  "query": {
    "bool": {
      "must": [
        { "match": { "customer.name": "Rob" } },
        { "match": { "customer.name": "Bygr" } }
      ],
      "must_not": [
        { "term": { "customer.status": "ACTIVE" } }
      ]
    }
  },
  "filter": {
    "bool": {
      "should": [
        { "term": { "status": "COMPLETE" } },
        { "exists": { "field": "status" } }
      ]
    }
  }
}

Match

Match is an ElasticSearch only expression and when used the query is automatically treated as a document store query.

For query beans the match() expression is available on properties of type string.

List<Order> orders =
  new QOrder()
    .customer.name.match("Rob")
    .findList();
{
  "query": {
    "match": {
      "customer.name": "Rob"
    }
  }
}

Multi match, Text simple etc

multiMatch(), textSimple(), textCommonTerms() and textQueryString() are all ElasticSearch only expression and using them automatically make the query a document store query.

These expression are "query level" expressions that apply to multiple fields or the entire document (_all field).

MultiMatch multiMatch =
  MultiMatch.fields("customer.name", "customer.notes")
    .opAnd()
    .type(MultiMatch.Type.PHRASE_PREFIX);

new QOrder()
    .multiMatch("Rob", multiMatch)
    .findList();
{"query": {
  "multi_match": {
    "query": "Rob",
    "fields": [
      "customer.name",
      "customer.notes"
    ],
    "type": "phrase_prefix",
    "operator": "and"
  }
}}

Text expressions

match

match maps to ElasticSearch match query.

multi match

multi match maps to ElasticSearch multi match query.

text simple

text simple maps to ElasticSearch simple query string query.

text query string

text query string maps to ElasticSearch query string query.

text common terms

text common terms maps to ElasticSearch common terms query.