require "date" require "time" require "treetop" require_relative "datatype_extras" require_relative "sql_treetop_load" require_relative "result_tree_to_hbase_converter" require_relative "result_tree_to_json_converter" # This module provides the methods necessary to parse valid SQL # sentences into: # # * Treetop Syntax Trees # * Hash Trees # * JSON Trees # * HBase (Thrift) # # It also allows to directly execute valid SQL sentences as Thrift methods. module HipsterSqlToHbase # Treetop base SQL parser. # # Forked from Scott Taylor's guillotine and perfected by Jean Lescure. # # https://www.github.com/smtlaissezfaire/guillotine/tree/master/lib/guillotine/parser # # https://www.github.com/jeanlescure/hipster_sql_to_hbase class SyntaxParser < SQLParser class ItemsNode < Treetop::Runtime::SyntaxNode def values items.values.unshift(item.value) end end class ItemNode < Treetop::Runtime::SyntaxNode def values [value] end def value text_value.to_sym end end end # This class provides the initial parsed result with the methods # necessary to be transformed into HBase (Thrift) or JSON, as well # as to be executed as a Thrift method. class ResultTree < Hash def initialize(hash) hash.each { |k,v| self[k] = v } end # Transforms itself into an HBase (Thrift) method. # # === example: # rt = HipsterSqlToHbase::ResultTree.new({...}) # rt.to_hbase #=> {:method => 'mutateRow', :arguments => {...}} def to_hbase HipsterSqlToHbase::ResultTreeToHbaseConverter.new().convert self end # Transforms itself into a JSON object. # # === example: # user = HipsterSqlToHbase::ResultTree.new({...}) # user.to_json #=> {:user => {:user_name => 'bob', :pass => 'bob1234', ...}} def to_json HipsterSqlToHbase::ResultTreeToJsonConverter.new().convert self end # Executes itself as an HBase (Thrift) method. # Short for running .to_hbase and then sending the result to the executor. # # === example: # my_call = HipsterSqlToHbase::ResultTree.new({...}) # my_call.execute # # if no arguments are passed the executor will use any previously set host # and port. def execute(host=nil,port=nil) self.to_hbase.execute(host,port) end end class << self # Generate a Treetop syntax tree from a valid, SQL string. # # === example: # HipsterSqlToHbase.parse_syntax "INSERT INTO users (user,password) VALUES ('user1','pass123'),('user2','2girls1pass')" # # === outputs: # #> def parse_syntax(string) HipsterSqlToHbase::SyntaxParser.new.parse(string.squish) end # Generate a Hash from a valid, SQL string. # # === example: # HipsterSqlToHbase.parse_hash "SELECT user,password FROM users WHERE id=1" # # === outputs: # { # :query_type=>:select, # :query_hash=>{ # :select=>["user", "password"], # :from=>"users", # :where=>[{:column=>"id", :condition=>:"=", :value=>1}], # :limit=>nil, # :order_by=>nil # } # } def parse_hash(string) syntax_tree = parse_syntax(string) return nil if syntax_tree.nil? { :query_type => syntax_tree.query_type, :query_hash => syntax_tree.tree } end # Generate a HipsterSqlToHbase::ResultTree from a valid, SQL string. # # === example: # HipsterSqlToHbase.parse_tree "SELECT user,password FROM users WHERE id=1" # # === outputs: # { # :query_type=>:select, # :query_hash=>{ # :select=>["user", "password"], # :from=>"users", # :where=>[{:column=>"id", :condition=>:"=", :value=>1}], # :limit=>nil, # :order_by=>nil # } # } # # === Note: the main difference between **parse_tree** and **parse_hash** is that a **ResultTree** can be further converted to Thrift calls while a **Hash** cannot. def parse_tree(string) HipsterSqlToHbase::ResultTree.new(parse_hash(string)) end # Generate a HipsterSqlToHbase::ThriftCallGroup from a valid, SQL string. # # === example: # HipsterSqlToHbase.parse "INSERT INTO `users` (`user`,`pass`) VALUES ('andy','w00dy'),('zaphod','b33bl3br0x')" # # === outputs: # [ # { # :method=>"mutateRow", # :arguments=>[ # "users", # "c6af1d5b-01d7-477c-9539-f35a1e0758e2", # [, ], # {} # ] # }, # { # :method=>"mutateRow", # :arguments=>[ # "users", # "b630cf3c-c969-420e-afd9-b646466a6743", # [, ], # {} # ] # } # ] # # === note: # # The resulting ThriftCallGroup can be executed simply by calling its .execute() method. def parse(string) result_tree = parse_tree(string) return nil if result_tree.nil? result_tree.to_hbase end # Generates and automatically executes a HipsterSqlToHbase::ThriftCallGroup # from a valid, SQL string. # # The returned value varies depending on the SQL query type (i.e. SELECT, INSERT, etc). def execute(string,host=nil,port=nil) parsed_tree = parse_tree(string) return nil if parsed_tree.nil? parsed_tree.execute(host,port) end end end