module Bmg module Sql class Relation include Bmg::Relation def initialize(type, builder, expr) @type = type @builder = builder @expr = expr end attr_accessor :type protected attr_reader :expr, :builder public def bind(binding) expr = before_use(self.expr) expr = Processor::Bind.new(binding, builder).call(expr) _instance(type, builder, expr) end def each(&bl) raise NotImplementedError end def delete raise NotImplementedError end def insert(arg) raise NotImplementedError end def update(arg) raise NotImplementedError end protected ## optimization def _allbut(type, butlist) preserved_key = self.type.knows_keys? && self.type.keys.find{|k| (k & butlist).empty? } expr = before_use(self.expr) expr = Processor::Clip.new(butlist, true, :is_table_dee, builder).call(expr) expr = Processor::Distinct.new(builder).call(expr) unless preserved_key _instance(type, builder, expr) end def _constants(type, cs) expr = before_use(self.expr) expr = Processor::Constants.new(cs, builder).call(expr) _instance(type, builder, expr) end def _join(type, right, on) if right_expr = extract_compatible_sexpr(right) right_expr = Processor::Requalify.new(builder).call(right_expr) expr = before_use(self.expr) expr = Processor::Join.new(right_expr, on, {}, builder).call(expr) _instance(type, builder, expr) else super end end def _left_join(type, right, on, default_right_tuple) if right_expr = extract_compatible_sexpr(right) right_expr = Processor::Requalify.new(builder).call(right_expr) expr = before_use(self.expr) expr = Processor::Join.new(right_expr, on, { kind: :left, default_right_tuple: default_right_tuple }, builder).call(expr) _instance(type, builder, expr) else super end end def _matching(type, right, on) if right_expr = extract_compatible_sexpr(right) expr = before_use(self.expr) expr = Processor::SemiJoin.new(right_expr, on, false, builder).call(expr) _instance(type, builder, expr) else super end end def _not_matching(type, right, on) if right_expr = extract_compatible_sexpr(right) expr = before_use(self.expr) expr = Processor::SemiJoin.new(right_expr, on, true, builder).call(expr) _instance(type, builder, expr) else super end end def _page(type, ordering, page_index, options) limit = options[:page_size] || Operator::Page::DEFAULT_OPTIONS[:page_size] offset = (page_index - 1) * limit expr = before_use(self.expr) expr = Processor::OrderBy.new(ordering, builder).call(expr) expr = Processor::LimitOffset.new(limit, offset, builder).call(expr) _instance(type, builder, expr) end def _project(type, attrlist) preserved_key = self.type.knows_keys? && self.type.keys.find{|k| k.all?{|a| attrlist.include?(a) } } expr = before_use(self.expr) expr = Processor::Clip.new(attrlist, false, :is_table_dee, builder).call(expr) expr = Processor::Distinct.new(builder).call(expr) unless preserved_key _instance(type, builder, expr) end def _rename(type, renaming) expr = before_use(self.expr) expr = Processor::Rename.new(renaming, builder).call(expr) _instance(type, builder, expr) end def _restrict(type, predicate) expr = before_use(self.expr) expr = Processor::Where.new(predicate, builder).call(expr) _instance(type, builder, expr) end def _summarize(type, by, defs) summarization = ::Bmg::Summarizer.summarization(defs) if can_compile_summarization?(summarization) expr = before_use(self.expr) expr = Processor::Summarize.new(by, summarization, builder).call(expr) _instance(type, builder, expr) else super end end def _transform(type, transformation, options) expr = before_use(self.expr) sup, unsup = Processor::Transform.split_supported(transformation){|x| [String, Integer, Float, Date, DateTime].include?(x) } return super if sup.nil? expr = Processor::Transform.new(sup, options, builder).call(expr) result = _instance(type, builder, expr) result = result.transform(unsup, options) if unsup result rescue Sql::NotSupportedError super end def can_compile_summarization?(summarization) summarization.values.all?{|s| [:avg, :count, :max, :min, :sum, :distinct_count].include?(s.to_summarizer_name) } end def _union(type, right, options) if right_expr = extract_compatible_sexpr(right) expr = before_use(self.expr) expr = Processor::Merge.new(:union, !!options[:all], right_expr, builder).call(expr) _instance(type, builder, expr) else super end end # Build a new relation instance for some new type & expression # # This method can be overriden by subclasses to provide their # own class instance def _instance(type, builder, expr) Relation.new(type, builder, expr) end # Provides subclasses with a chance to manipulate the expression # before it is reused in another one, following an algebra method # call def before_use(expr) expr end # Given a Relation operand (typically a right operand in a binary # expression), extract the associated `expr` if it is compatible # with self for optimization (e.g. same underlying engine). # # May return nil if operand should not be considered for further # optimization def extract_compatible_sexpr(operand) nil end public def to_sql expr.to_sql("", Dialect.default) end def to_ast [:sql, to_sql] end def to_s "(sql #{to_sql})" end alias :inspect :to_s end # class Relation end # module Sql end # module Bmg