Skip to content

Instantly share code, notes, and snippets.

@bigfleet
Last active October 12, 2018 06:15
Show Gist options
  • Select an option

  • Save bigfleet/4625050 to your computer and use it in GitHub Desktop.

Select an option

Save bigfleet/4625050 to your computer and use it in GitHub Desktop.
My citrus effort
grammar Querying
rule statement
(compound_predicate | predicate)
end
rule compound_predicate
(predicate space* operator_and_predicate){
Wither::CompoundPredicate.new(predicate.value,
operator_and_predicate.group_operator.value,
operator_and_predicate.predicate.value)
}
end
rule operator_and_predicate
(group_operator space* predicate)
end
rule predicate
(group | within_phrase | in_phrase | binary_phrase)
end
rule group
(lparen statement rparen){
statement.value
}
end
rule within_phrase
('within' space* number space* 'of' space* keyword){
Wither::WithinPhrase.new(number.value, keyword.value)
}
end
rule in_phrase
(keyword space* 'in' space* '[' value_list ']') {
Wither::InPhrase.new(keyword.value, value_list.value)
}
end
rule binary_phrase
(keyword space* binary_operator space* avalue){
Wither::BinaryPhrase.new(keyword.value, binary_operator.value, avalue.value)
}
end
rule term
(keyword | value)
end
rule value_list
(avalue ',' value_list | avalue)
end
rule avalue
(literal | number)
end
rule keyword
(unreserved 1*)
end
rule literal
('"'ALPHA 1*'"')
end
rule number
(DIGIT 1*)
end
## Lexical syntax
# unreserved = ALPHA / DIGIT / "-" / "." / "_"
rule unreserved
(ALPHA | "_")
end
# reserved = gen-delims / sub-delims
rule reserved
group_operator | unary_operator | grouper
end
rule group_operator
"&" | "|" | "-"
end
rule binary_operator
"<=" | ">=" | "=" | "~" | ">" | "<"
end
rule DIGIT
[0-9]
end
rule ALPHA
[a-zA-Z]
end
rule lparen '(' space* end
rule rparen ')' space* end
rule space [ \t\n\r] end
end
module Wither
class Engine
class << self
def activate
Citrus.require("#{::Rails.root}/config/wither.citrus")
end
def config(args={})
Wither::Config.instance(args)
end
def parse(filter)
parse_text(filter.text)
end
def parse_text(text)
Querying.parse(text).value
end
end
end
class Walker
attr_accessor :ast
def initialize(ast)
@ast = ast
end
def walk
@ast.keywords
end
end
class Config
attr_accessor :args
def initialize(args)
@args = HashWithIndifferentAccess.new(args)
end
def load
self
end
def self.keywords(ast)
ast.keywords
end
def self.instance(args)
Wither::Config.new(args).load
end
end
class Statement
attr_accessor :captures
def initialize(captures)
@captures = captures
end
end
class CompoundPredicate
attr_accessor :lhs, :operator, :rhs
def initialize(p1, o, p2)
@lhs, @operator, @rhs = p1, o, p2
end
def keywords
(@lhs.keywords + @rhs.keywords).sort.uniq
end
end
class BinaryPhrase
attr_accessor :keyword, :operator, :value
def initialize(k, o, v)
@keyword, @operator, @value = k,o,v
end
def keywords
[@keyword]
end
end
class InPhrase
attr_accessor :keyword, :value_list
def initialize(k, vl)
@keyword, @value_list = k,vl
end
def keywords
[@keyword]
end
end
class WithinPhrase
attr_accessor :magnitude, :origin
def initialize(m,o)
@magnitude, @origin = m,o
end
def keywords
[@origin]
end
end
class Filter
attr_accessor :text
def initialize(text)
@text = text
end
def parse
Wither::Engine.parse(self)
end
def to_sql
parse.to_sql
end
end
def self.filter(text)
parse(text).to_sql
end
def self.parse(text)
Wither::Filter.new(text).parse
end
end
require 'spec_helper'
describe Wither do
describe Wither::Engine do
def attempt_to_parse(text)
lambda{
Wither::Engine.parse_text(text)
}.should_not raise_error(Citrus::ParseError)
end
context "parsing" do
context "binary phrases" do
it "should parse > operators" do
attempt_to_parse("green_eggs > 1")
end
it "should parse < operators" do
attempt_to_parse("green_eggs < 1")
end
it "should parse >= operators" do
attempt_to_parse("green_eggs >= 1")
end
it "should parse <= operators" do
attempt_to_parse("green_eggs <= 1")
end
it "should parse = operators on numbers" do
attempt_to_parse("green_eggs = 2")
end
it "should parse = operators on alphas" do
attempt_to_parse("email_type = \"work\"")
end
it "should parse ~ operators" do
attempt_to_parse("note_text ~ \"taken\"")
end
end
context "specialty phrases" do
it "should parse proximity" do
attempt_to_parse("within 2 of user")
end
it "should parse single-term 'in' phrases" do
attempt_to_parse('state in ["KY"]')
end
it "should parse multi-term 'in' phrases" do
attempt_to_parse('state in ["KY","TN"]')
end
end
context "grouped phrases" do
it "should parse & operators" do
attempt_to_parse("corn > 500 & within 2 of origin")
end
it "should parse | operators" do
attempt_to_parse("corn > 500 | wheat > 500")
end
it "should parse - operators" do
attempt_to_parse('corn > 500 - status in ["purchased","pending"]')
end
end
context "precedent hints" do
it "should allow simple containing" do
attempt_to_parse("(corn > 500)")
end
it "should allow them first" do
attempt_to_parse("(corn > 500 & wheat > 500) | status = \"considering\"")
end
it "should allow them last" do
attempt_to_parse("status = \"considering\" | (corn > 500 & wheat > 500)")
end
it "should allow them grouped" do
attempt_to_parse("(corn > 500 -(phone_number_count > 2 - do_not_call = 1))")
end
end
pending "should consider starts|ends_with, exists, is_true, is_false"
end
context "keyword analysis" do
def keywords(text)
Wither::Engine.parse_text(text).keywords
end
context "for binary phrases" do
subject{ keywords("green_eggs > 1") }
it{ should have(1).thing }
it{ should include("green_eggs") }
end
context "for in phrases" do
subject{ keywords('state in ["KY"]') }
it{ should have(1).thing }
it{ should include("state") }
end
context "for proximity phrases" do
subject{ keywords("within 2 of origin") }
it{ should have(1).thing }
it{ should include("origin") }
end
context "for compound predicates" do
subject{ keywords("corn > 500 | wheat > 500") }
it{ should have(2).things }
it{ should include("corn") }
it{ should include("wheat") }
end
context "for groupings of compound predicates" do
subject{ keywords("status = \"considering\" | (corn > 500 & wheat > 500)") }
it{ should have(3).things }
it{ should include("status") }
it{ should include("corn") }
it{ should include("wheat") }
end
end
end
describe Wither::Config do
before(:each) do
@config = Wither::Config.new(WITHER_CONFIG)
end
end
describe Wither::Filter do
pending "should engage engine" do
target = "WITH corn as (select * from ag.entity_crops) select entity_id from corn where acres > 499"
Wither.filter("corn > 499").should == target
end
end
end
@bigfleet

Copy link
Copy Markdown
Author

I'd be open to https://gist.github.com/4625050#file-wither_spec-rb-L82 being anything, basically, but can't get a handle on anything lower than Statement no matter what's returned.

No matter what I put in the rule statement phrase, I never seem to get anything except statements, and can never end up invoking methods on anything more primitive in the language than that. All help appreciated.

@bigfleet

Copy link
Copy Markdown
Author

You can safely ignore the curly brace parts within the current grammar, I can eliminate them, or change them to raise "hell", and can never get them to invoke.

@bigfleet

Copy link
Copy Markdown
Author

The revision you are currently looking at works!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment