-
-
Save exlee/4cab3b6962fbeb69d98d32b2b345cff0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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