diff --git a/.gitignore b/.gitignore
index 73633ee..1b861d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
pkg/**
+.*.swp
smql.gemspec
diff --git a/Rakefile b/Rakefile
index 4245ce4..c9ac2a2 100644
--- a/Rakefile
+++ b/Rakefile
@@ -15,6 +15,7 @@ begin
gem.add_dependency 'activerecord'
gem.add_dependency 'activesupport'
gem.add_dependency 'json'
+ gem.add_dependency 'methodphitamine'
end
Jeweler::GemcutterTasks.new
rescue LoadError
diff --git a/VERSION b/VERSION
index 4e379d2..2ec68a9 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.0.2
+0.0.4.3
diff --git a/lib/smql_to_ar.rb b/lib/smql_to_ar.rb
index d144320..6d252af 100644
--- a/lib/smql_to_ar.rb
+++ b/lib/smql_to_ar.rb
@@ -15,6 +15,18 @@
# along with this program. If not, see .
class SmqlToAR
+ module Assertion
+ def raise_unless cond, exception = nil, *args
+ cond, exception, *args = yield. cond, exception, *args if block_given?
+ raise exception || Exception, *args unless cond
+ end
+
+ def raise_if cond, exception = nil, *args
+ cond, exception, *args = yield. cond, exception, *args if block_given?
+ raise exception || Exception, *args if cond
+ end
+ end
+
include ActiveSupport::Benchmarkable
############################################################################r
# Exceptions
@@ -77,19 +89,27 @@ class SmqlToAR
end
end
- class OnlyOrderOnBaseError < SMQLError
+ class RootOnlyFunctionError < SMQLError
def initialize path
super :path => path
end
end
+ class ConColumnError < SMQLError
+ def initialize expected, got
+ super :expected => expected, :got => got
+ end
+ end
+
class BuilderError < Exception; end
#############################################################################
# Model der Relation `rel` von `model`
def self.model_of model, rel
- model.reflections[ rel.to_sym].andand.klass
+ rel = rel.to_sym
+ r = model.reflections[ rel].andand.klass
+ r.nil? && :self == rel ? model : r
end
# Eine Spalte in einer Tabelle, relativ zu `Column#model`.
@@ -103,7 +123,7 @@ class SmqlToAR
def initialize model, *col
@model = model
@last_model = nil
- *@path, @col = Array.wrap( col).collect {|s| s.to_s.split /[.\/]/ }.flatten.collect &:to_sym
+ *@path, @col = *Array.wrap( col).collect( &it.to_s.split( /[.\/]/)).flatten.collect( &:to_sym).reject( &it==:self)
end
def last_model
@@ -113,9 +133,12 @@ class SmqlToAR
def each
model = @model
@path.each do |rel|
- model = SmqlToAR.model_of model, rel
- return false unless model
- yield rel, model
+ rel = rel.to_sym
+ unless :self == rel
+ model = SmqlToAR.model_of model, rel
+ return false unless model
+ yield rel, model
+ end
end
model
end
@@ -140,19 +163,23 @@ class SmqlToAR
def joins builder = nil, table = nil, &exe
pp = []
table = Array.wrap table
- exe ||= builder ? lambda {|j, m| builder.join table+j, m} : Array.method( :[])
+ exe ||= builder ? lambda {|j, m| builder.joins table+j, m} : Array.method( :[])
collect do |rel, model|
pp.push rel
exe.call pp, model
end
end
- def to_a() @path+[@col] end
+ def self?() !@col end
+ def length() @path.length+(self.self? ? 0 : 1) end
+ def size() @path.size+(self.self? ? 0 : 1) end
+ def to_a() @path+(self.self? ? [] : [@col]) end
def to_s() to_a.join '.' end
def to_sym() to_s.to_sym end
def to_json() to_s end
def inspect() "#" end
- def relation() SmqlToAR.model_of last_model, @col end
+ def relation() self.self? ? model : SmqlToAR.model_of( last_model, @col) end
def allowed?() ! self.protected? end
+ def child?() @path.empty? and !!relation end
end
attr_reader :model, :query, :conditions, :builder, :order
@@ -179,6 +206,23 @@ class SmqlToAR
#p model: @model, query: @query
end
+ def self.models models
+ models = Array.wrap models
+ r = Hash.new {|h,k| h[k] = {} }
+ while model = models.tap( &:uniq!).pop
+ refls = model.respond_to?( :reflections) && model.reflections
+ refls && refls.each do |name, refl|
+ r[model.name][name] = case refl
+ when ActiveRecord::Reflection::ThroughReflection then {:macro => refl.macro, :model => refl.klass.name, :through => refl.through_reflection.name}
+ when ActiveRecord::Reflection::AssociationReflection then {:macro => refl.macro, :model => refl.klass.name}
+ else raise "Ups: #{refl.class}"
+ end
+ models.push refl.klass unless r.keys.include? refl.klass.name
+ end
+ end
+ r
+ end
+
def parse
#benchmark 'SMQL parse' do
@conditions = ConditionTypes.try_parse @model, @query
@@ -214,4 +258,12 @@ class SmqlToAR
def self.to_ar *params
new( *params).to_ar
end
+
+ def self.reload_library
+ lib_dir = File.dirname __FILE__
+ fj = lambda {|*a| File.join lib_dir, *a }
+ load fj.call( 'smql_to_ar.rb')
+ load fj.call( 'smql_to_ar', 'condition_types.rb')
+ load fj.call( 'smql_to_ar', 'query_builder.rb')
+ end
end
diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb
index 84fb850..05ce5c6 100644
--- a/lib/smql_to_ar/condition_types.rb
+++ b/lib/smql_to_ar/condition_types.rb
@@ -25,28 +25,44 @@ class SmqlToAR
# Nimmt eine Klasse ein Objekt an, so soll diese Klasse instanziert werden.
# Alles weitere siehe Condition.
module ConditionTypes
+ extend SmqlToAR::Assertion
+
class < [:givenname, :surname, :nick]
def split_keys k
k.split( '|').collect &:to_sym
end
+ def conditions &e
+ unless block_given?
+ r = Enumerator.new( self, :conditions)
+ s = self
+ r.define_singleton_method :[] do |k|
+ s.conditions.select {|c| c::Operator === k }
+ end
+ return r
+ end
+ constants.each do |c|
+ next if :Condition == c
+ c = const_get c
+ next if Condition === c
+ yield c
+ end
+ end
+
# Eine Regel parsen.
# Ex: Person, "givenname=", "Peter"
def try_parse_it model, colop, val
r = nil
#p :try_parse => { :model => model, :colop => colop, :value => val }
- constants.each do |c|
- next if :Condition == c
- c = const_get c
- next if Condition === c
- raise UnexpectedColOpError.new( model, colop, val) unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/
+ conditions.each do |c|
+ raise_unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/, UnexpectedColOpError.new( model, colop, val)
col, op = $1, $2
col = split_keys( col).collect {|c| Column.new model, c }
r = c.try_parse model, col, op, val
break if r
end
- raise UnexpectedError.new( model, colop, val) unless r
+ raise_unless r, UnexpectedError.new( model, colop, val)
r
end
@@ -72,16 +88,24 @@ class SmqlToAR
end
class Condition
+ include SmqlToAR::Assertion
+ extend SmqlToAR::Assertion
attr_reader :value, :cols
Operator = nil
Expected = []
Where = nil
- # Versuche das Objekt zu erkennen. Operator und Expected muessen passen.
- # Passt das Object, die Klasse instanzieren.
- def self.try_parse model, cols, op, val
- #p :self => name, :try_parse => op, :cols => cols, :with => self::Operator, :value => val, :expected => self::Expected, :model => model.name
- new model, cols, val if self::Operator === op and self::Expected.any? {|i| i === val }
+ class < name, :try_parse => op, :cols => cols, :with => self::Operator, :value => val, :expected => self::Expected, :model => model.name
+ new model, cols, val if self::Operator === op and self::Expected.any?( &it === val)
+ end
+
+ def inspect
+ "#{self.name}(:operator=>#{self::Operator.inspect}, :expected=>#{self::Expected.inspect}, :where=>#{self::Where.inspect})"
+ end
end
def initialize model, cols, val
@@ -93,6 +117,10 @@ class SmqlToAR
verify
end
+ def inspect
+ "#<#{self.class.name}:0x#{(self.object_id<<1).to_s 16} model: #{self.class.name}, cols: #{@cols.inspect}, value: #{@value.inspect}>"
+ end
+
def verify
@cols.each do |col|
verify_column col
@@ -103,19 +131,19 @@ class SmqlToAR
# Gibt es eine Spalte diesen Namens?
# Oder: Gibt es eine Relation diesen Namens? (Hier nicht der Fall)
def verify_column col
- raise NonExistingColumnError.new( %w[Column], col) unless col.exist_in?
+ raise_unless col.exist_in?, NonExistingColumnError.new( %w[Column], col)
end
# Modelle koennen Spalten/Relationen verbieten mit Model#smql_protected.
# Dieses muss ein Object mit #include?( name_als_string) zurueckliefern,
# welches true fuer verboten und false fuer, erlaubt steht.
def verify_allowed col
- raise ProtectedColumnError.new( col) if col.protected?
+ raise_if col.protected?, ProtectedColumnError.new( col)
end
# Erstelle alle noetigen Klauseln. builder nimmt diese entgegen,
- # wobei builder.join, builder.select, builder.where und builder.wobs von interesse sind.
- # mehrere Schluessel bedeuten, dass die Values _alle_ zutreffen muessen, wobei die Schluessel geodert werden.
+ # wobei builder.joins, builder.select, builder.where und builder.wobs von interesse sind.
+ # mehrere Schluessel bedeuten, dass die Values _alle_ zutreffen muessen, wobei die Schluessel geODERt werden.
# Ex:
# 1) {"givenname=", "Peter"} #=> givenname = 'Peter'
# 2) {"givenname=", ["Peter", "Hans"]} #=> ( givenname = 'Peter' OR givenname = 'Hans' )
@@ -128,15 +156,16 @@ class SmqlToAR
@cols.each do |col|
col.joins builder, table
col = builder.column table+col.path, col.col
- builder.where *values.keys.collect {|vid| self.class::Where % [ col, vid.to_s ] }
+ builder.where values.keys.collect {|vid| self.class::Where % [ col, vid.to_s ] }
end
else
+ b2 = SmqlToAR::And.new builder
values.keys.each do |vid|
- builder.where *@cols.collect {|col|
+ b2.where SmqlToAR::Or[ *@cols.collect {|col|
col.joins builder, table
col = builder.column table+col.path, col.col
self.class::Where % [ col, vid.to_s ]
- }
+ }]
end
end
self
@@ -185,50 +214,94 @@ class SmqlToAR
In = simple_condition NotIn, '|=', '%s IN (%s)', [Array]
In2 = simple_condition In, '', nil, [Array]
- NotEqual = simple_condition Condition, /\!=|<>/, "%s <> %s", [Array, String, Numeric]
+ NotEqual = simple_condition Condition, '!=', "%s <> %s", [Array, String, Numeric]
+ NotEqual2 = simple_condition Condition, '<>', "%s <> %s", [Array, String, Numeric]
GreaterThanOrEqual = simple_condition Condition, '>=', "%s >= %s", [Array, Numeric]
LesserThanOrEqual = simple_condition Condition, '<=', "%s <= %s", [Array, Numeric]
+
+ # Examples:
+ # { 'articles=>' => { id: 1 } }
+ # { 'articles=>' => [ { id: 1 }, { id: 2 } ] }
class EqualJoin @model.reflections.keys
- raise NonExistingRelationError.new( %w[Relation], col) unless refl
+ raise_unless col.relation, NonExistingRelationError.new( %w[Relation], col)
end
def build builder, table
@cols.each do |col, sub|
+ model, *sub = sub
t = table + col.path + [col.col]
- #p sub: sub
- #p col: col, joins: col.joins
- col.joins.each {|j, m| builder.join table+j, m }
- builder.join t, SmqlToAR.model_of( col.last_model, col.col)
- sub[1..-1].each {|one| one.build builder, t }
+ col.joins.each {|j, m| builder.joins table+j, m }
+ builder.joins t, model
+ b2 = 1 == sub.length ? builder : Or.new( builder)
+ sub.each {|i| i.collect( &it.build( And.new( b2), t)); p 'or' => b2 }
end
self
end
end
+
+ # Takes to Queries.
+ # First Query will be a Subquery, second a regular query.
+ # Example:
+ # Person.smql 'sub.articles:' => [{'limit:' => 1, 'order:': 'updated_at desc'}, {'content~' => 'some text'}]
+ # Person must have as last Article (compared by updated_at) owned by Person a Artive which has 'some text' in content.
+ # The last Article needn't to have 'some text' has content, the subquery takes it anyway.
+ # But the second query compares to it and never to any other Article, because these are filtered by first query.
+ # The difference to
+ # Person.smql :articles => {'content~' => 'some text', 'limit:' => 1, 'order:': 'updated_at desc'}
+ # is, second is not allowed (limit and order must be in root) and this means something like
+ # "Person must have the Article owned by Person which has 'some text' in content.
+ # limit and order has no function in this query and this article needn't to be the last."
+=begin
+ class SubEqualJoin < EqualJoin
+ Operator = '()'
+ Expected = [lambda {|x| x.kind_of?( Array) and (1..2).include?( x.length) and x.all?( &it.kind_of?( Hash))}]
+
+ def initialize model, cols, val
+ super model, cols, val[1]
+ # sub: model, subquery, sub(condition)
+ @cols.each {|col, sub| sub[ 1..-1] = SmqlToAR.new( col.relation, val[0]).parse, *sub[-1] }
+ end
+
+ def verify_column col
+ raise_unless col.child?, ConColumnError.new( [:Column], col)
+ end
+
+ def build builder, table
+ @cols.each do |col, sub|
+ t = table+col.to_a
+ builder.sub_joins t, col, *sub[0..1]
+ #ap sub: sub[2..-1]
+ sub[2..-1].each &it.build( builder, t)
+ end
+ self
+ end
+ end
+=end
+
Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric]
Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric]
GreaterThan = simple_condition Condition, '>', "%s > %s", [Array, Numeric]
LesserThan = simple_condition Condition, '<', "%s < %s", [Array, Numeric]
NotIlike = simple_condition Condition, '!~', "%s NOT ILIKE %s", [Array, String]
Ilike = simple_condition Condition, '~', "%s ILIKE %s", [Array, String]
+ Exists = simple_condition Condition, '', '%s IS NOT NULL', [TrueClass]
+ NotExists = simple_condition Condition, '', '%s IS NULL', [FalseClass]
- ####### No Operator #######
Join = simple_condition EqualJoin, '', nil, [Hash]
InRange2 = simple_condition InRange, '', nil, [Range]
class Select < Condition
@@ -236,7 +309,7 @@ class SmqlToAR
Expected = [nil]
def verify_column col
- raise NonExistingSelectableError.new( col) unless col.exist_in? or SmqlToAR.model_of( col.last_model, col.col)
+ raise_unless col.exist_in? || SmqlToAR.model_of( col.last_model, col.col), NonExistingSelectableError.new( col)
end
def build builder, table
@@ -258,13 +331,19 @@ class SmqlToAR
Expected = [String, Array, Hash, Numeric, nil]
class Function
+ include SmqlToAR::Assertion
Name = nil
Expected = []
attr_reader :model, :func, :args
- def self.try_parse model, func, args
- #SmqlToAR.logger.info( { try_parse: [func,args]}.inspect)
- self.new model, func, args if self::Name === func and self::Expected.any? {|e| e === args }
+ class <#{self::Name}, :expected=>#{self::Expected})"
+ end
end
def initialize model, func, args
@@ -277,22 +356,19 @@ class SmqlToAR
Expected = [String, Array, Hash, nil]
def initialize model, func, args
- #SmqlToAR.logger.info( {args: args}.inspect)
args = case args
when String then [args]
when Array, Hash then args.to_a
when nil then nil
else raise 'Oops'
end
- #SmqlToAR.logger.info( {args: args}.inspect)
args.andand.collect! do |o|
o = Array.wrap o
col = Column.new model, o.first
o = 'desc' == o.last.to_s.downcase ? :DESC : :ASC
- raise NonExistingColumnError.new( [:Column], col) unless col.exist_in?
+ raise_unless col.exist_in?, NonExistingColumnError.new( [:Column], col)
[col, o]
end
- #SmqlToAR.logger.info( {args: args}.inspect)
super model, func, args
end
@@ -302,22 +378,39 @@ class SmqlToAR
col, o = o
col.joins builder, table
t = table + col.path
- #raise OnlyOrderOnBaseError.new( t) unless 1 == t.length
+ #raise_unless 1 == t.length, RootOnlyFunctionError.new( t)
builder.order t, col.col, o
end
end
end
+ class Limit < Function
+ Name = :limit
+ Expected = [Fixnum]
+
+ def build builder, table
+ raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
+ builder.limit = Array.wrap(@args).first.to_i
+ end
+ end
+
+ class Offset < Function
+ Name = :offset
+ Expected = [Fixnum]
+
+ def build builder, table
+ raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
+ builder.offset = Array.wrap(@args).first.to_i
+ end
+ end
+
def self.new model, col, val
- #SmqlToAR.logger.info( { function: col.first.to_sym }.inspect)
r = nil
constants.each do |c|
next if [:Function, :Where, :Expected, :Operator].include? c
c = const_get c
next if Function === c or not c.respond_to?( :try_parse)
- #SmqlToAR.logger.info( {f: c}.inspect)
r = c.try_parse model, col.first.to_sym, val
- #SmqlToAR.logger.info( {r: r}.inspect)
break if r
end
r
diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb
index dc3bd4e..ff3980d 100644
--- a/lib/smql_to_ar/query_builder.rb
+++ b/lib/smql_to_ar/query_builder.rb
@@ -22,36 +22,41 @@ class SmqlToAR
class Vid
attr_reader :vid
def initialize( vid) @vid = vid end
- def to_s() ":c#{@vid}" end
- def to_sym() "c#{@vid}".to_sym end
+ def to_s() ":smql_c#{@vid}" end
+ def to_sym() "smql_c#{@vid}".to_sym end
alias sym to_sym
def to_i() @vid end
end
- attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins
- attr_accessor :logger
+ attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins, :prefix, :_vid
+ attr_accessor :logger, :limit, :offset
- def initialize model
+ def initialize model, prefix = nil
+ @prefix = "smql"
@logger = SmqlToAR.logger
@table_alias = Hash.new do |h, k|
- k = Array.wrap k
- h[k] = "smql,#{k.join(',')}"
+ j = Array.wrap( k).compact
+ h[k] = h.key?(j) ? h[j] : "#{@prefix},#{j.join(',')}"
end
- @_vid, @_where, @_wobs, @model, @quoter = 0, [], {}, model, model.connection
+ @_vid, @_where, @_wobs, @model, @quoter = 0, SmqlToAR::And[], {}, model, model.connection
@base_table = [model.table_name.to_sym]
@table_alias[ @base_table] = @base_table.first
t = quote_table_name @table_alias[ @base_table]
- @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [], [], []
+ @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [@base_table], [], []
@table_model = {@base_table => @model}
end
def vid() Vid.new( @_vid+=1) end
+ def inspect
+ "#<#{self.class.name}:#{"0x%x"% (self.object_id<<1)}|#{@prefix}:#{@base_table}:#{@model} vid=#{@_vid} where=#{@_where} wobs=#{@_wobs} select=#{@_select} aliases=#{@_table_alias}>"
+ end
+
# Jede via where uebergebene Condition wird geodert und alle zusammen werden geundet.
# "Konjunktive Normalform". Allerdings duerfen Conditions auch Komplexe Abfragen enthalten.
- # Ex: builder.where( 'a = a', 'b = c').where( 'c = d', 'e = e').where( 'x = y').where( '( m = n AND o = p )', 'f = g')
+ # Ex: builder.where( ['a = a', 'b = c']).where( ['c = d', 'e = e']).where( 'x = y').where( ['( m = n AND o = p )', 'f = g'])
# #=> WHERE ( a = a OR b = c ) AND ( c = d OR e = e ) AND x = y ( ( m = n AND o = p ) OR f = g )
- def where *cond
+ def where cond
@_where.push cond
self
end
@@ -74,32 +79,54 @@ class SmqlToAR
end
def build_join orig, pretable, table, prekey, key
- " JOIN #{quote_table_name orig.to_sym} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} "
+ " LEFT JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} "
end
- def join table, model
- return self if @_joined.include? table # Already joined
- pretable = table[0...-1]
+ def sub_joins table, col, model, query
+ prefix, base_table = "#{@prefix}_sub", col.relation.table_name
+ join_ table, model, "(#{query.build( prefix).ar.to_sql})"
+ end
+
+ def join_ table, model, query, pretable = nil
+ pretable ||= table[0...-1]
@table_model[ table] = model
premodel = @table_model[ pretable]
t = @table_alias[ table]
pt = quote_table_name @table_alias[ table[ 0...-1]]
refl = premodel.reflections[table.last]
- case refl.macro
- when :has_many
- @_joins += build_join model.table_name, pretable, t, premodel.primary_key, refl.primary_key_name
- when :belongs_to
- @_joins += build_join model.table_name, pretable, t, refl.primary_key_name, premodel.primary_key
- when :has_and_belongs_to_many
- jointable = [','] + table
- @_joins += build_join refl.options[:join_table], pretable, @table_alias[jointable], premodel.primary_key, refl.primary_key_name
- @_joins += build_join model.table_name, jointable, t, refl.association_foreign_key, refl.association_primary_key
- else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}"
+ case refl
+ when ActiveRecord::Reflection::ThroughReflection
+ through = refl.through_reflection
+ throughtable = table[0...-1]+[through.name.to_sym]
+ srctable = throughtable+[refl.source_reflection.name]
+ @table_model[ srctable] = model
+ @table_alias[ table] = @table_alias[ srctable]
+ join_ throughtable, through.klass, quote_table_name( through.table_name)
+ join_ srctable, refl.klass, query, throughtable
+ when ActiveRecord::Reflection::AssociationReflection
+ case refl.macro
+ when :has_many, :has_one
+ @_joins += build_join query, pretable, t, premodel.primary_key, refl.primary_key_name
+ when :belongs_to
+ @_joins += build_join query, pretable, t, refl.primary_key_name, premodel.primary_key
+ when :has_and_belongs_to_many
+ jointable = [','] + table
+ @_joins += build_join refl.options[:join_table], pretable, @table_alias[jointable], premodel.primary_key, refl.primary_key_name
+ @_joins += build_join query, jointable, t, refl.association_foreign_key, refl.association_primary_key
+ else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}"
+ end
+ else raise BuilderError, "Unkown reflection type: #{refl.class.name}"
end
- @_joined.push table
self
end
+ def joins table, model
+ table = table.flatten.compact
+ return self if @_joined.include? table # Already joined
+ join_ table, model, quote_table_name( model.table_name)
+ @_joined.push table
+ end
+
def includes table
@_includes.push table
self
@@ -117,30 +144,22 @@ class SmqlToAR
self
end
- class Dummy
- def method_missing m, *a, &e
- #p :dummy => m, :pars => a, :block => e
- self
- end
- end
-
def build_ar
- where_str = @_where.collect do |w|
- w = Array.wrap w
- 1 == w.length ? w.first : "( #{w.join( ' OR ')} )"
- end.join ' AND '
+ where_str = @_where.type_correction!.optimize!.build_where
incls = {}
@_includes.each do |inc|
b = incls
inc[1..-1].collect {|rel| b = b[rel] ||= {} }
end
- @logger.debug incls: incls, joins: @_joins
@model = @model.
select( @_select.join( ', ')).
joins( @_joins).
where( where_str, @_wobs).
order( @_order.join( ', ')).
includes( incls)
+ @model = @model.limit @limit if @limit
+ @model = @model.offset @offset if @offset
+ @model
end
def fix_calculate
@@ -159,4 +178,72 @@ class SmqlToAR
@model
end
end
+
+ class SubBuilder < Array
+ attr_reader :parent, :_where
+ delegate :wobs, :joins, :includes, :sub_joins, :vid, :quote_column_name, :quoter, :quote_table_name, :column, :to => :parent
+
+ def initialize parent, tmp = false
+ @parent = parent
+ @parent.where self unless @parend.nil? && tmp
+ end
+
+ def new parent, tmp = false
+ super parent, tmp
+ #return parent if self.class == parent.class
+ #super parent
+ end
+
+ alias where push
+
+ def type_correction!
+ collect! do |sub|
+ if sub.kind_of? Array
+ sub = default[ *sub] unless sub.respond_to?( :type_correction!)
+ sub.type_correction!
+ end
+ sub
+ end
+ self
+ end
+
+ def optimize!
+ ext = []
+ collect! do |sub|
+ sub = sub.optimize! if sub.kind_of? Array
+ if self.class == sub.class
+ ext.push *sub
+ nil
+ elsif sub.blank?
+ nil
+ else
+ sub
+ end
+ end.compact!
+ push *ext
+ self
+ end
+
+ def inspect
+ "#{self.class.name.sub( /.*::/, '')}[ #{collect(&:inspect).join ', '}]"
+ end
+ def default() SmqlToAR::And end
+ def default_new( parent) default.new self, parent, false end
+ def collect_build_where
+ collect {|x| x.respond_to?( :build_where) ? x.build_where : x.to_s }
+ end
+ end
+
+ class And < SubBuilder
+ def default; SmqlToAR::Or; end
+ def build_where
+ collect_build_where.join ' AND '
+ end
+ end
+
+ class Or < SubBuilder
+ def build_where
+ collect_build_where.join ' OR '
+ end
+ end
end