Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Elasticsearch query parser experiments
module Elastomer
module Query
class ElasticsearchQueryParser < LuceneQueryParser
rule(:unbounded_range) { gte | lte | gt | lt }
#TODO range groups e.g. age:(>=10 AND < 20) age:(+>=10 +<20)
#TODO eliminate duplication
rule(:field) { word.as(:field) >> str(':') >> (((group | regexp | inclusive_range.as(:inclusive_range) | exclusive_range.as(:exclusive_range)) >> boost.maybe) | ((phrase | term) >> (distance | boost).maybe)) }
end
end
end
module Elastomer
module Query
class LuceneQueryParser < Parslet::Parser
rule(:space) { match('\s').repeat(1) }
rule(:word) { match('\w').repeat(1) }
rule(:digit) { match('[0-9]') }
rule(:integer) { digit.repeat(1) }
rule(:float) { integer >> str('.') >> integer }
rule(:reserved) { match('[+\-!(){}[]^"~*?:\\/]') | string('&&') | string('||') }
rule(:wildcard) { str('*') }
rule(:operator) { (str('AND') | str('OR')).as(:operator) }
rule(:distance) { str('~') >> digit.as(:distance) }
rule(:boost) { str('^') >> (integer | float).as(:boost) }
#TODO disallow unmatched quotes
rule(:term) { (match('[^?*]') >> match('[\w?*]').repeat).as(:term) >> (distance | boost).maybe }
#TODO allow escaped quotes
rule(:phrase) { str('"') >> match('[^"]').repeat(1).as(:phrase) >> str('"') >> (distance | boost).maybe }
rule(:group) { str('(') >> space.maybe >> query.as(:group) >> space.maybe >> str(')') >> boost.maybe }
rule(:gt) { str('>') >> space.maybe >> word.as(:gt) }
rule(:lt) { str('<') >> space.maybe >> word.as(:lt) }
rule(:gte) { str('>=') >> space.maybe >> word.as(:gte) }
rule(:lte) { str('<=') >> space.maybe >> word.as(:lte) }
rule(:bounded_range) { (word | wildcard).as(:from) >> space >> str('TO') >> space >> (word | wildcard).as(:to) }
rule(:inclusive_range) { str('[') >> space.maybe >> bounded_range >> space.maybe >> str(']') }
rule(:exclusive_range) { str('{') >> space.maybe >> bounded_range >> space.maybe >> str('}') }
#TODO allow escaped /
rule(:regexp) { str('/') >> match('[^/]').repeat(1).as(:regexp) >> str('/') >> boost.maybe}
rule(:unary_operator) { str('+').as(:required) | str('-').as(:prohibited) | (str('NOT').as(:prohibited) >> space) }
rule(:field) { word.as(:field) >> str(':') >> (((group | regexp | inclusive_range.as(:inclusive_range) | exclusive_range.as(:exclusive_range)) >> boost.maybe) | ((phrase | term) >> (distance | boost).maybe)) }
rule(:clause) { unary_operator.maybe >> (group | field | phrase | regexp | term) }
rule(:query) { space.maybe >> clause >> (space >> (operator >> space >> clause | clause)).repeat >> space.maybe }
root :query
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.