Skip to content

Instantly share code, notes, and snippets.

@exlee
Created November 20, 2017 14:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save exlee/4cab3b6962fbeb69d98d32b2b345cff0 to your computer and use it in GitHub Desktop.
Save exlee/4cab3b6962fbeb69d98d32b2b345cff0 to your computer and use it in GitHub Desktop.
module WWTK
module SearchQueryLogic
class MopedTranslator
def initialize(model, *default_search_fields)
@selectors = {}
@parser = SearchQuery.new
@dsf = default_search_fields
@model = model
end
def set_selector(name, &procc)
raise "Cannot overwrite selector" if @selectors.has_key? name
@selectors[name] = procc
end
def run(query)
if query.empty?
@model.all
else
parsed = @parser.parse(query)
q = translate(parsed)
@model.all.where(q)
end
rescue => e
[]
end
private
def translate(query)
method_name = "translate_" + query.class.name.split("::").last
method_name = method_name.to_sym
send method_name, query
end
def translate_And(query)
left = translate(query.left)
right = translate(query.right)
{:$and => [left,right]}
end
def translate_Or(query)
left = translate(query.left)
right = translate(query.right)
{:$or => [left,right]}
end
def translate_Assoc(query)
left = translate(query.left)
right = translate(query.right)
{left => right}
end
def translate_JoinedAssoc(query)
left = translate(query.left)
right = translate(query.right)
if @selectors.has_key? left
item_set = @selectors[left].call right
item_ids = item_set.map{|i| i._id}
{:_id => { :$in => item_ids }}
else
{:_id => true}
end
end
def translate_Not(query)
item = translate(query.item)
# Small odity
# The oddity is caused by a fact
# that Mongo expects {:attribute => {$ne => value}}
# instead of {$ne => {:attribute => value}}
for k in item.keys do
old_val = item[k]
if old_val.instance_of? String
item[k] = {:$ne => old_val}
else
item[k] = {:$not => old_val}
end
end
item
end
def translate_Terminal(query)
assoc_cal = caller[1][/`([^']*)'/, 1]
return query.item if assoc_cal.ends_with? 'Assoc'
if not query.item.instance_of? Regexp
tuple = @dsf.map {|field| {field => /.*#{query.item}.*/ui}}
{@dsf => /.*#{query.item}.*/ui}
else
tuple = @dsf.map {|field| {field => query.item}}
end
{:$or => tuple}
end
end
class BinaryOperator
attr_reader :left,:right
def initialize(left,right)
@left = left
@right = right
end
def print(p=0,o=2)
puts " "*p + self.class.name.split(/::/).last
@left.print(p+o,o)
@right.print(p+o,o)
end
end
class UnaryOperator
attr_reader :item
def initialize(item)
@item = item
end
def print(p=0,o=2)
puts " "*p + self.class.name.split(/::/).last
@item.print(p+o,o)
end
end
class Terminal < UnaryOperator
def print(p=0,o=2)
puts " "*p + self.class.name.split(/::/).last + ": "+@item.to_s
end
end
class Or < BinaryOperator
end
class And < BinaryOperator
end
class Assoc < BinaryOperator
end
class JoinedAssoc < BinaryOperator
end
class Not < UnaryOperator
end
class SearchQuery < Whittle::Parser
# Operators
rule(:and => / and /ui) % :right ^ 1
rule(:or => / or /iu)% :right ^ 1
rule(:assoc => /:\s?/u)% :left ^ 3
rule("-")% :left ^4
rule(:not => /not /ui)% :left ^ 4
rule('!')
rule('"')
rule("'")
rule("(") ^1
rule(")") ^1
rule(:whitespace => /\s+/u)
rule(:word => /[<>_\.\p{Word}\d@][\-<>_\.\p{Word}\d@]*/u).as{|s| Terminal.new(s) }
rule(:string => /(['"])[^\1]+\1/u).as {|s| Terminal.new(s.gsub(/['"]/u, '')) }
rule(:regexp => /\/[^\/]+\/i?/u).as {|s| Terminal.new(Regexp.new(eval(s))) }
rule(:expr) do |r|
r["(", :expr, ")"].as {|_,e,_| e}
r[:binary_operator] ^1
r[:unary_operator] ^1
r[:terminal] ^2
end
rule(:unary_operator) do |r|
r["-", :expr].as {|_,e| Not.new(e)}
r[:not, :expr].as {|_,e| Not.new(e)}
end
rule(:binary_operator) do |r|
r[:expr, :and, :expr].as{|a,_,b| And.new(a,b) }
r[:expr, :or, :expr].as{|a,_,b| Or.new(a,b)}
r[:word, :assoc, :terminal].as{|a,_,b| Assoc.new(a,b) }
r["!", :word, :assoc, :terminal].as {|_,a,_,b| JoinedAssoc.new(a,b) }
r[:expr, :whitespace, :expr].as{|a,_,b| And.new(a,b)}
end
rule(:terminal) do |r|
r[:string]
r[:word]
r[:regexp]
end
start(:expr)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment