class EsQueryBuilder::Parser

Public: The class which has a responsibility for creatign a query.

Note that the term “query” has two different meanings in the terminology of Elasticsearch. One represents how to retrieve documents from Elasticsearch and it consists of query and filter, so that is to say the other is a part of previous one. In this file, “query” and “query hash” represents the former and the latter respectively:

"query" = "query hash" + "filter hash"

Public Class Methods

new(all_query_fields: '_all', hierarchy_fields: [], nested_fields: {}, child_fields: {}) click to toggle source

Public: Construct the parser object.

all_query_fields - A String or an Array of Strings for searching usual

query terms (default: '_all').

hierarchy_fields - An Array of Strings which treats the trailing slash

character as a hierarchy (default: []).

Returns nothing.

# File lib/es-query-builder/parser.rb, line 21
def initialize(all_query_fields: '_all', hierarchy_fields: [],
               nested_fields: {}, child_fields: {})
  @all_query_fields = all_query_fields
  @hierarchy_fields = hierarchy_fields
  @nested_fields = nested_fields
  @child_fields = child_fields
end

Public Instance Methods

parse(tokens) click to toggle source

Public: Parse the given tokens and build a query hash.

tokens - An Array of Tokens.

Returns a Hash for Elasticsearch client or nil.

# File lib/es-query-builder/parser.rb, line 34
def parse(tokens)
  connect_queries(build_queries(tokens))
end

Private Instance Methods

build_filter_hash(filter_tokens) click to toggle source

Internal: Build a filter parameter hash by query tokens

filter_tokens - An Array of filter Tokens.

Returns a Hash represents a filter hash.

# File lib/es-query-builder/parser.rb, line 137
def build_filter_hash(filter_tokens)
  return {} if filter_tokens.empty?
  must, must_not = create_bool_filters(filter_tokens)
  if must.size == 1 && must_not.empty?
    # Term filter is cached by default.
    must.first
  else
    bool = {}
    bool[:must]     = must     if must.size > 0
    bool[:must_not] = must_not if must_not.size > 0
    # Bool filter is not cached by default.
    { bool: bool.merge(_cache: true) }
  end
end
build_queries(tokens) click to toggle source

Internal: Convert the given tokens into sequence of queries.

tokens - An Array of Tokens.

Returns an Array of Hashes. Each hash represents a query.

# File lib/es-query-builder/parser.rb, line 45
def build_queries(tokens)
  split_by_or_token(tokens).map do |or_less_tokens|
    query_hash = build_query_hash(or_less_tokens.select(&:query?))
    filter_hash = build_filter_hash(or_less_tokens.select(&:filter?))
    create_query(query_hash, filter_hash)
  end
end
build_query_hash(query_tokens) click to toggle source

Internal: Build a query hash by query tokens

query_tokens - An Array of query Tokens.

Returns a Hash represents a query hash.

# File lib/es-query-builder/parser.rb, line 119
def build_query_hash(query_tokens)
  return { match_all: {} } if query_tokens.empty?
  must, must_not = create_bool_queries(query_tokens)
  if must.size == 1 && must_not.empty?
    must.first
  else
    bool = {}
    bool[:must]     = must     if must.size > 0
    bool[:must_not] = must_not if must_not.size > 0
    { bool: bool }
  end
end
connect_queries(queries) click to toggle source

Internal: Merge sequence of queries into a single query.

queries - An Array of Hashes. Eash hash represents a query.

Returns a Hash or nil.

# File lib/es-query-builder/parser.rb, line 58
def connect_queries(queries)
  case queries.size
  when 0
    nil
  when 1
    queries.first
  else
    {
      bool: {
        should: queries
      }
    }
  end
end
create_bool_filters(filter_tokens) click to toggle source

Internal: Create boolean filter based on the filter matches. If a field query in hierarchy fields ends with ‘/’, it matches to all descendant terms.

query_tokens - An Array of filter Tokens.

Examples

# When 'tag:foo'
create_bool_filters([...])
# => [[ { bool: { should: [{ term: { tag: 'foo' } }, { prefix: { tag: 'foo/' } }] } }], []]

# When '-tag:foo'
create_bool_filters([...])
# => [[], [], [{ term: { tag: 'foo' } }, { prefix: { tag: 'foo/' } }]]

Returns an Array consists of must, should and must_not filters arrays.

# File lib/es-query-builder/parser.rb, line 243
def create_bool_filters(filter_tokens)
  must, must_not = [], []
  filter_tokens.each do |token|
    token.term.split.each do |term|
      if @hierarchy_fields.include?(token.field)
        if token.minus?
          must_not << { prefix: { token.field => term.downcase + '/' } }
          must_not << { term: { token.field => term.downcase } }
        else
          must << {
            bool: {
              should: [
                { prefix: { token.field => term.downcase + '/' } },
                # Exactly matches to the tag.
                { term: { token.field => term.downcase } },
              ]
            }
          }
        end
      else
        cond = token.minus? ? must_not : must
        cond << { term: { token.field => term.downcase } }
      end
    end
  end
  [must, must_not]
end
create_bool_queries(query_tokens) click to toggle source

Internal: Create boolean query based with the given query tokens.

query_tokens - An Array of query Tokens.

Returns an Array consists of must and must_not query arrays.

# File lib/es-query-builder/parser.rb, line 157
def create_bool_queries(query_tokens)
  must, must_not = [], []
  query_tokens.each do |token|
    queries = token.minus? ? must_not : must

    queries <<
      # When the field is not given or invalid one, search by all fields.
      if token.field.nil?
        should = []
        should << create_match_query(@all_query_fields, token.term)
        @nested_fields.each do |nested_path, nested_field|
          should << create_nested_match_query(nested_path, nested_field, token.term)
        end
        @child_fields.each do |child_type, child_field|
          should << create_has_child_match_query(child_type, child_field, token.term)
        end
        connect_queries(should)

      # When the specify nested field
      elsif nested_field = @nested_fields[token.field_namespace]
        create_nested_match_query(token.field_namespace, nested_field, token.term)

      # When the specify child field
      elsif child_field = @child_fields[token.field_namespace]
        create_has_child_match_query(token.field_namespace, child_field, token.term)

      # When the specify standard field
      else
        create_match_query(token.field, token.term)
      end
  end
  [must, must_not]
end
create_has_child_match_query(child_type, field, term) click to toggle source
# File lib/es-query-builder/parser.rb, line 217
def create_has_child_match_query(child_type, field, term)
  {
    has_child: {
      type: child_type.to_s,
      query: create_match_query(field, term)
    }
  }
end
create_match_query(field, term) click to toggle source
# File lib/es-query-builder/parser.rb, line 191
def create_match_query(field, term)
  if field.is_a?(String)
    {
      match: {
        field => term
      }
    }
  else
    {
      multi_match: {
        fields: field,
        query: term
      }
    }
  end
end
create_nested_match_query(path, field, term) click to toggle source
# File lib/es-query-builder/parser.rb, line 208
def create_nested_match_query(path, field, term)
  {
    nested: {
      path: path.to_s,
      query: create_match_query(field, term)
    }
  }
end
create_query(query_hash, filter_hash) click to toggle source

Internal: Connect given query hash and filter hash objects.

query_hash - A Hash represents a query hash. filter_hash - A Hash represents a filter hash.

Returns a Hash represents a query.

# File lib/es-query-builder/parser.rb, line 101
def create_query(query_hash, filter_hash)
  if filter_hash.size > 0
    {
      filtered: {
        query: query_hash,
        filter: filter_hash
      }
    }
  else
    query_hash
  end
end
split_by_or_token(tokens) click to toggle source

Internal: Divide the given tokens array into sub arrays by ‘or’ token.

tokens - An Array of Search::QueryBuilder::Token.

Examples

split_by_or_token([<Query>, <OR>, <Query>, <Filter>])
#=> [[<Query>], [<Query>, <Filter>]]

Returns an Array of Arrays of Tokens.

# File lib/es-query-builder/parser.rb, line 83
def split_by_or_token(tokens)
  expressions = [[]]
  tokens.each do |token|
    if token.or?
      expressions << []
    else
      expressions.last << token
    end
  end
  expressions.select { |e| e.size > 0 }
end