#------------------------------------------------------------------------------- # Copyright (c) 2013 National ICT Australia Limited (NICTA). # This software may be used and distributed solely under the terms of the MIT license (License). # You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT. # By downloading or using this software you accept the terms and the liability disclaimer in the License. #------------------------------------------------------------------------------- require 'rubygems' require 'rexml/document' require 'time' require 'logger' require 'sequel' require 'omf_base/lobject' require 'omf_oml' # module OMF::OML # module Sequel; end # end module OMF::OML::Sequel module Server class Query < OMF::Base::LObject def self.parse(xmls, repoFactory = RepositoryFactory.new, logger = Logger.new(STDOUT)) if xmls.kind_of? String doc = REXML::Document.new(xmls) root = doc.root else root = xmls end unless root.name == 'query' raise "XML fragment needs to start with 'query' but does start with '#{root.name}" end q = self.new(root, repoFactory, logger) q.relation end def initialize(queryEl, repoFactory, logger) @queryEl = queryEl @repoFactory = repoFactory || RepositoryFactory.new @logger = logger || Logger.new(STDOUT) @tables = {} @lastRel = nil @offset = 0 @limit = 0 queryEl.children.each do |el| @lastRel = parse_el(el, @lastRel) end if @limit > 0 @lastRel = @lastRel.limit(@limit, @offset) end end def each(&block) # sel_mgr = relation # unless sel_mgr.kind_of? SelectionManager # raise "Can only be called on SELECT statement" # end # puts sel_mgr.engine relation.each(&block) end def relation raise "No query defined, yet" unless @lastRel @lastRel end # Requested format for result. Default is 'xml' def rformat @queryEl.attributes['rformat'] || 'xml' end def parse_el(el, lastRel) if (el.kind_of? REXML::Text) # skip return lastRel end args = parse_args(el) @logger.debug "CHILD #{el.name}" # keep the last table for this level to be used # to create proper columns. # NOTE: This is not fool-proof but we need columns # to later resolve the column type. # name = el.name.downcase if lastRel.nil? case name when /repository/ lastRel = repo = parse_repository(el) @tables = repo.tables @logger.debug "Created repository: #{lastRel}" else raise "Need to start with 'table' declaration, but does with '#{name}'" end elsif name == 'table' lastRel = parse_table(el) elsif name == 'project' # turn all arguments into proper columns # cols = convert_to_cols(args) lastRel = lastRel.select(*args) # elsif lastRel.kind_of?(::Arel::Table) && name == 'as' # # keep track of all created tables # lastRel = lastRel.alias(*args) # @repository.add_table(args[0], lastRel) elsif name == 'skip' @offset = args[0].to_i elsif name == 'take' @limit = args[0].to_i else @logger.debug "Sending '#{name}' to #{lastRel.class}" lastRel = lastRel.send(name, *args) end @logger.debug "lastRel for <#{el}> is #{lastRel.class}" lastRel end def parse_repository(el) @repository = @repoFactory.create_from_xml(el, @logger) end # Return the arguments defined in @parentEl as array def parse_args(parentEl) args = [] parentEl.children.each do |el| next if (el.kind_of? REXML::Text) unless el.name == 'arg' raise "Expected argument definition but got element '#{el.name}" end args << parse_arg(el) end args end # Return the arguments defined in @parentEl as array def parse_arg(pel) res = nil #col = nil pel.children.each do |el| if (el.kind_of? REXML::Text) val = el.value next if val.strip.empty? # skip text between els return parse_arg_primitive(pel, val) else name = el.name.downcase case name when /col/ res = parse_column(el) when /eq/ if res.nil? raise "Missing 'col' definiton before 'eq'." end p = parse_args(el) unless p.length == 1 raise "'eq' can only hnadle 1 argument, but is '#{p.inspect}'" end res = {res => p[0]} else raise "Need to be 'col' declaration, but is '#{name}'" end end end res end def parse_arg_primitive(pel, value) type = pel.attributes['type'] || 'string' case type when /string/ value when /boolean/ value.downcase == 'true' || value == '1' when /decimal/ value.to_i when /double/ value.to_f when /dateTime/ Time.xmlschema else raise "Unknown arg type '#{type}" end end def convert_to_cols(args) args.collect do |arg| if arg.kind_of? String table = @repository.get_first_table() raise "Unknown table for column '#{arg}'" unless table table[arg] else arg end end end #