AR-through-reflections in query_builder; SmqlToAR.models returns the reflections-graph; limit, offset, sub-query "()" (not sub-conditions). unstable 0.3
This commit is contained in:
parent
8144c72284
commit
db5bbcb93a
6 changed files with 180 additions and 50 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
pkg/**
|
pkg/**
|
||||||
|
.*.swp
|
||||||
smql.gemspec
|
smql.gemspec
|
||||||
|
|
1
Rakefile
1
Rakefile
|
@ -15,6 +15,7 @@ begin
|
||||||
gem.add_dependency 'activerecord'
|
gem.add_dependency 'activerecord'
|
||||||
gem.add_dependency 'activesupport'
|
gem.add_dependency 'activesupport'
|
||||||
gem.add_dependency 'json'
|
gem.add_dependency 'json'
|
||||||
|
gem.add_dependency 'methodphitamine'
|
||||||
end
|
end
|
||||||
Jeweler::GemcutterTasks.new
|
Jeweler::GemcutterTasks.new
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
0.0.2
|
0.0.3
|
||||||
|
|
|
@ -15,6 +15,18 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
class SmqlToAR
|
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
|
include ActiveSupport::Benchmarkable
|
||||||
############################################################################r
|
############################################################################r
|
||||||
# Exceptions
|
# Exceptions
|
||||||
|
@ -77,12 +89,18 @@ class SmqlToAR
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class OnlyOrderOnBaseError < SMQLError
|
class RootOnlyFunctionError < SMQLError
|
||||||
def initialize path
|
def initialize path
|
||||||
super :path => path
|
super :path => path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class ConColumnError < SMQLError
|
||||||
|
def initialize expected, got
|
||||||
|
super :expected => expected, :got => got
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class BuilderError < Exception; end
|
class BuilderError < Exception; end
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
@ -103,7 +121,7 @@ class SmqlToAR
|
||||||
def initialize model, *col
|
def initialize model, *col
|
||||||
@model = model
|
@model = model
|
||||||
@last_model = nil
|
@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)
|
||||||
end
|
end
|
||||||
|
|
||||||
def last_model
|
def last_model
|
||||||
|
@ -146,6 +164,8 @@ class SmqlToAR
|
||||||
exe.call pp, model
|
exe.call pp, model
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
def length() @path.length+1 end
|
||||||
|
def size() @path.size+1 end
|
||||||
def to_a() @path+[@col] end
|
def to_a() @path+[@col] end
|
||||||
def to_s() to_a.join '.' end
|
def to_s() to_a.join '.' end
|
||||||
def to_sym() to_s.to_sym end
|
def to_sym() to_s.to_sym end
|
||||||
|
@ -153,6 +173,7 @@ class SmqlToAR
|
||||||
def inspect() "#<Column: #{model} #{to_s}>" end
|
def inspect() "#<Column: #{model} #{to_s}>" end
|
||||||
def relation() SmqlToAR.model_of last_model, @col end
|
def relation() SmqlToAR.model_of last_model, @col end
|
||||||
def allowed?() ! self.protected? end
|
def allowed?() ! self.protected? end
|
||||||
|
def child?() @path.empty? and !!relation end
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :model, :query, :conditions, :builder, :order
|
attr_reader :model, :query, :conditions, :builder, :order
|
||||||
|
@ -179,6 +200,23 @@ class SmqlToAR
|
||||||
#p model: @model, query: @query
|
#p model: @model, query: @query
|
||||||
end
|
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
|
def parse
|
||||||
benchmark 'SMQL parse' do
|
benchmark 'SMQL parse' do
|
||||||
@conditions = ConditionTypes.try_parse @model, @query
|
@conditions = ConditionTypes.try_parse @model, @query
|
||||||
|
@ -187,11 +225,11 @@ class SmqlToAR
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
def build
|
def build prefix = nil, base_table = nil
|
||||||
benchmark 'SMQL build query' do
|
benchmark 'SMQL build query' do
|
||||||
@builder = QueryBuilder.new @model
|
@builder = QueryBuilder.new @model, prefix, base_table
|
||||||
table = @builder.base_table
|
table = @builder.base_table
|
||||||
@conditions.each {|condition| condition.build builder, table }
|
@conditions.each &it.build( builder, table)
|
||||||
end
|
end
|
||||||
#p builder: @builder
|
#p builder: @builder
|
||||||
self
|
self
|
||||||
|
|
|
@ -25,6 +25,8 @@ class SmqlToAR
|
||||||
# Nimmt eine Klasse ein Objekt an, so soll diese Klasse instanziert werden.
|
# Nimmt eine Klasse ein Objekt an, so soll diese Klasse instanziert werden.
|
||||||
# Alles weitere siehe Condition.
|
# Alles weitere siehe Condition.
|
||||||
module ConditionTypes
|
module ConditionTypes
|
||||||
|
extend SmqlToAR::Assertion
|
||||||
|
|
||||||
class <<self
|
class <<self
|
||||||
# Ex: 'givenname|surname|nick' => [:givenname, :surname, :nick]
|
# Ex: 'givenname|surname|nick' => [:givenname, :surname, :nick]
|
||||||
def split_keys k
|
def split_keys k
|
||||||
|
@ -40,13 +42,13 @@ class SmqlToAR
|
||||||
next if :Condition == c
|
next if :Condition == c
|
||||||
c = const_get c
|
c = const_get c
|
||||||
next if Condition === c
|
next if Condition === c
|
||||||
raise UnexpectedColOpError.new( model, colop, val) unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/
|
raise_unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/, UnexpectedColOpError.new( model, colop, val)
|
||||||
col, op = $1, $2
|
col, op = $1, $2
|
||||||
col = split_keys( col).collect {|c| Column.new model, c }
|
col = split_keys( col).collect {|c| Column.new model, c }
|
||||||
r = c.try_parse model, col, op, val
|
r = c.try_parse model, col, op, val
|
||||||
break if r
|
break if r
|
||||||
end
|
end
|
||||||
raise UnexpectedError.new( model, colop, val) unless r
|
raise_unless r, UnexpectedError.new( model, colop, val)
|
||||||
r
|
r
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -72,6 +74,8 @@ class SmqlToAR
|
||||||
end
|
end
|
||||||
|
|
||||||
class Condition
|
class Condition
|
||||||
|
include SmqlToAR::Assertion
|
||||||
|
extend SmqlToAR::Assertion
|
||||||
attr_reader :value, :cols
|
attr_reader :value, :cols
|
||||||
Operator = nil
|
Operator = nil
|
||||||
Expected = []
|
Expected = []
|
||||||
|
@ -81,7 +85,7 @@ class SmqlToAR
|
||||||
# Passt das Object, die Klasse instanzieren.
|
# Passt das Object, die Klasse instanzieren.
|
||||||
def self.try_parse model, cols, op, val
|
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
|
#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 }
|
new model, cols, val if self::Operator === op and self::Expected.any?( &it === val)
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize model, cols, val
|
def initialize model, cols, val
|
||||||
|
@ -103,14 +107,14 @@ class SmqlToAR
|
||||||
# Gibt es eine Spalte diesen Namens?
|
# Gibt es eine Spalte diesen Namens?
|
||||||
# Oder: Gibt es eine Relation diesen Namens? (Hier nicht der Fall)
|
# Oder: Gibt es eine Relation diesen Namens? (Hier nicht der Fall)
|
||||||
def verify_column col
|
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
|
end
|
||||||
|
|
||||||
# Modelle koennen Spalten/Relationen verbieten mit Model#smql_protected.
|
# Modelle koennen Spalten/Relationen verbieten mit Model#smql_protected.
|
||||||
# Dieses muss ein Object mit #include?( name_als_string) zurueckliefern,
|
# Dieses muss ein Object mit #include?( name_als_string) zurueckliefern,
|
||||||
# welches true fuer verboten und false fuer, erlaubt steht.
|
# welches true fuer verboten und false fuer, erlaubt steht.
|
||||||
def verify_allowed col
|
def verify_allowed col
|
||||||
raise ProtectedColumnError.new( col) if col.protected?
|
raise_if col.protected?, ProtectedColumnError.new( col)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Erstelle alle noetigen Klauseln. builder nimmt diese entgegen,
|
# Erstelle alle noetigen Klauseln. builder nimmt diese entgegen,
|
||||||
|
@ -196,31 +200,64 @@ class SmqlToAR
|
||||||
super( *pars)
|
super( *pars)
|
||||||
cols = {}
|
cols = {}
|
||||||
@cols.each do |col|
|
@cols.each do |col|
|
||||||
col_model = SmqlToAR.model_of col.last_model, col.col
|
col_model = col.relation
|
||||||
#p col_model: col_model.to_s, value: @value
|
|
||||||
cols[col] = [col_model] + ConditionTypes.try_parse( col_model, @value)
|
cols[col] = [col_model] + ConditionTypes.try_parse( col_model, @value)
|
||||||
end
|
end
|
||||||
@cols = cols
|
@cols = cols
|
||||||
end
|
end
|
||||||
|
|
||||||
def verify_column col
|
def verify_column col
|
||||||
refl = SmqlToAR.model_of col.last_model, col.col
|
raise_unless col.relation, NonExistingRelationError.new( %w[Relation], col)
|
||||||
#p refl: refl, model: @model.name, col: col, :reflections => @model.reflections.keys
|
|
||||||
raise NonExistingRelationError.new( %w[Relation], col) unless refl
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def build builder, table
|
def build builder, table
|
||||||
@cols.each do |col, sub|
|
@cols.each do |col, sub|
|
||||||
t = table + col.path + [col.col]
|
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 }
|
col.joins.each {|j, m| builder.join table+j, m }
|
||||||
builder.join t, SmqlToAR.model_of( col.last_model, col.col)
|
builder.join t, sub[0]
|
||||||
sub[1..-1].each {|one| one.build builder, t }
|
sub[1..-1].each &it.build( builder, t)
|
||||||
end
|
end
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
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."
|
||||||
|
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 }
|
||||||
|
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
|
||||||
|
p t: t, sub: sub
|
||||||
|
builder.sub_join t, col, *sub[0..1]
|
||||||
|
sub[2..-1].each &it.build( builder, t)
|
||||||
|
end
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric]
|
Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric]
|
||||||
Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric]
|
Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric]
|
||||||
GreaterThan = simple_condition Condition, '>', "%s > %s", [Array, Numeric]
|
GreaterThan = simple_condition Condition, '>', "%s > %s", [Array, Numeric]
|
||||||
|
@ -236,7 +273,7 @@ class SmqlToAR
|
||||||
Expected = [nil]
|
Expected = [nil]
|
||||||
|
|
||||||
def verify_column col
|
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
|
end
|
||||||
|
|
||||||
def build builder, table
|
def build builder, table
|
||||||
|
@ -258,13 +295,14 @@ class SmqlToAR
|
||||||
Expected = [String, Array, Hash, Numeric, nil]
|
Expected = [String, Array, Hash, Numeric, nil]
|
||||||
|
|
||||||
class Function
|
class Function
|
||||||
|
include SmqlToAR::Assertion
|
||||||
Name = nil
|
Name = nil
|
||||||
Expected = []
|
Expected = []
|
||||||
attr_reader :model, :func, :args
|
attr_reader :model, :func, :args
|
||||||
|
|
||||||
def self.try_parse model, func, args
|
def self.try_parse model, func, args
|
||||||
SmqlToAR.logger.info( { try_parse: [func,args]}.inspect)
|
SmqlToAR.logger.info( { try_parse: [func,args]}.inspect)
|
||||||
self.new model, func, args if self::Name === func and self::Expected.any? {|e| e === args }
|
self.new model, func, args if self::Name === func and self::Expected.any?( &it === args)
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize model, func, args
|
def initialize model, func, args
|
||||||
|
@ -277,22 +315,19 @@ class SmqlToAR
|
||||||
Expected = [String, Array, Hash, nil]
|
Expected = [String, Array, Hash, nil]
|
||||||
|
|
||||||
def initialize model, func, args
|
def initialize model, func, args
|
||||||
SmqlToAR.logger.info( {args: args}.inspect)
|
|
||||||
args = case args
|
args = case args
|
||||||
when String then [args]
|
when String then [args]
|
||||||
when Array, Hash then args.to_a
|
when Array, Hash then args.to_a
|
||||||
when nil then nil
|
when nil then nil
|
||||||
else raise 'Oops'
|
else raise 'Oops'
|
||||||
end
|
end
|
||||||
SmqlToAR.logger.info( {args: args}.inspect)
|
|
||||||
args.andand.collect! do |o|
|
args.andand.collect! do |o|
|
||||||
o = Array.wrap o
|
o = Array.wrap o
|
||||||
col = Column.new model, o.first
|
col = Column.new model, o.first
|
||||||
o = 'desc' == o.last.to_s.downcase ? :DESC : :ASC
|
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]
|
[col, o]
|
||||||
end
|
end
|
||||||
SmqlToAR.logger.info( {args: args}.inspect)
|
|
||||||
super model, func, args
|
super model, func, args
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -302,12 +337,32 @@ class SmqlToAR
|
||||||
col, o = o
|
col, o = o
|
||||||
col.joins builder, table
|
col.joins builder, table
|
||||||
t = table + col.path
|
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
|
builder.order t, col.col, o
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class Limit < Function
|
||||||
|
Name = :limit
|
||||||
|
Expected = [Fixnum]
|
||||||
|
|
||||||
|
def build builder, table
|
||||||
|
raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
|
||||||
|
builder.limit @args
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Offset < Function
|
||||||
|
Name = :offset
|
||||||
|
Expected = [Fixnum]
|
||||||
|
|
||||||
|
def build builder, table
|
||||||
|
raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
|
||||||
|
builder.offset @args
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.new model, col, val
|
def self.new model, col, val
|
||||||
SmqlToAR.logger.info( { function: col.first.to_sym }.inspect)
|
SmqlToAR.logger.info( { function: col.first.to_sym }.inspect)
|
||||||
r = nil
|
r = nil
|
||||||
|
@ -315,9 +370,7 @@ class SmqlToAR
|
||||||
next if [:Function, :Where, :Expected, :Operator].include? c
|
next if [:Function, :Where, :Expected, :Operator].include? c
|
||||||
c = const_get c
|
c = const_get c
|
||||||
next if Function === c or not c.respond_to?( :try_parse)
|
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
|
r = c.try_parse model, col.first.to_sym, val
|
||||||
SmqlToAR.logger.info( {r: r}.inspect)
|
|
||||||
break if r
|
break if r
|
||||||
end
|
end
|
||||||
r
|
r
|
||||||
|
|
|
@ -22,23 +22,24 @@ class SmqlToAR
|
||||||
class Vid
|
class Vid
|
||||||
attr_reader :vid
|
attr_reader :vid
|
||||||
def initialize( vid) @vid = vid end
|
def initialize( vid) @vid = vid end
|
||||||
def to_s() ":c#{@vid}" end
|
def to_s() ":smql_c#{@vid}" end
|
||||||
def to_sym() "c#{@vid}".to_sym end
|
def to_sym() "smql_c#{@vid}".to_sym end
|
||||||
alias sym to_sym
|
alias sym to_sym
|
||||||
def to_i() @vid end
|
def to_i() @vid end
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins
|
attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins, :prefix, :_vid
|
||||||
attr_accessor :logger
|
attr_accessor :logger
|
||||||
|
|
||||||
def initialize model
|
def initialize model, prefix = nil, base_table
|
||||||
|
@prefix = "smql"
|
||||||
@logger = SmqlToAR.logger
|
@logger = SmqlToAR.logger
|
||||||
@table_alias = Hash.new do |h, k|
|
@table_alias = Hash.new do |h, k|
|
||||||
k = Array.wrap k
|
k = Array.wrap k
|
||||||
h[k] = "smql,#{k.join(',')}"
|
h[k] = "#{@prefix},#{k.join(',')}"
|
||||||
end
|
end
|
||||||
@_vid, @_where, @_wobs, @model, @quoter = 0, [], {}, model, model.connection
|
@_vid, @_where, @_wobs, @model, @quoter = 0, [], {}, model, model.connection
|
||||||
@base_table = [model.table_name.to_sym]
|
@base_table = base_table.blank? ? [model.table_name.to_sym] : Array.wrap( base_table)
|
||||||
@table_alias[ @base_table] = @base_table.first
|
@table_alias[ @base_table] = @base_table.first
|
||||||
t = quote_table_name @table_alias[ @base_table]
|
t = quote_table_name @table_alias[ @base_table]
|
||||||
@_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [], [], []
|
@_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [], [], []
|
||||||
|
@ -74,32 +75,57 @@ class SmqlToAR
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_join orig, pretable, table, prekey, key
|
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} "
|
" JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} "
|
||||||
end
|
end
|
||||||
|
|
||||||
def join table, model
|
def sub_join table, col, model, query
|
||||||
return self if @_joined.include? table # Already joined
|
pp [:sub_join, table, col. model, query]
|
||||||
pretable = table[0...-1]
|
prefix, base_table = "#{@prefix}_sub", col.col
|
||||||
|
join_ table, model, "(#{query.build( prefix, base_table).tap{|q| p :sub_join => q }.ar.to_sql})"
|
||||||
|
end
|
||||||
|
|
||||||
|
def join_ table, model, query, pretable = nil
|
||||||
|
pp [:join_, table, model, query]
|
||||||
|
pretable ||= table[0...-1]
|
||||||
@table_model[ table] = model
|
@table_model[ table] = model
|
||||||
premodel = @table_model[ pretable]
|
premodel = @table_model[ pretable]
|
||||||
t = @table_alias[ table]
|
t = @table_alias[ table]
|
||||||
pt = quote_table_name @table_alias[ table[ 0...-1]]
|
pt = quote_table_name @table_alias[ table[ 0...-1]]
|
||||||
|
pp premodel: premodel, table: table
|
||||||
refl = premodel.reflections[table.last]
|
refl = premodel.reflections[table.last]
|
||||||
case refl.macro
|
case refl
|
||||||
when :has_many
|
when ActiveRecord::Reflection::ThroughReflection
|
||||||
@_joins += build_join model.table_name, pretable, t, premodel.primary_key, refl.primary_key_name
|
through = refl.through_reflection
|
||||||
when :belongs_to
|
pp refl: refl
|
||||||
@_joins += build_join model.table_name, pretable, t, refl.primary_key_name, premodel.primary_key
|
throughtable = table[0...-1]+[through.name.to_sym]
|
||||||
when :has_and_belongs_to_many
|
srctable = throughtable+[refl.source_reflection.name]
|
||||||
jointable = [','] + table
|
@table_model[ srctable] = model
|
||||||
@_joins += build_join refl.options[:join_table], pretable, @table_alias[jointable], premodel.primary_key, refl.primary_key_name
|
@table_alias[ table] = @table_alias[ srctable]
|
||||||
@_joins += build_join model.table_name, jointable, t, refl.association_foreign_key, refl.association_primary_key
|
join_ throughtable, through.klass, quote_table_name( through.table_name)
|
||||||
else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}"
|
join_ srctable, refl.klass, query, throughtable
|
||||||
|
when ActiveRecord::Reflection::AssociationReflection
|
||||||
|
case refl.macro
|
||||||
|
when :has_many
|
||||||
|
@_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
|
end
|
||||||
@_joined.push table
|
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def join table, model
|
||||||
|
return self if @_joined.include? table # Already joined
|
||||||
|
join_ table, model, quote_table_name( model.table_name)
|
||||||
|
@_joined.push table
|
||||||
|
end
|
||||||
|
|
||||||
def includes table
|
def includes table
|
||||||
@_includes.push table
|
@_includes.push table
|
||||||
self
|
self
|
||||||
|
@ -114,6 +140,14 @@ class SmqlToAR
|
||||||
@_order.push "#{column table, col} #{:DESC == o ? :DESC : :ASC}"
|
@_order.push "#{column table, col} #{:DESC == o ? :DESC : :ASC}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def limit count
|
||||||
|
@_limit = count
|
||||||
|
end
|
||||||
|
|
||||||
|
def offset count
|
||||||
|
@_offset = count
|
||||||
|
end
|
||||||
|
|
||||||
class Dummy
|
class Dummy
|
||||||
def method_missing m, *a, &e
|
def method_missing m, *a, &e
|
||||||
#p :dummy => m, :pars => a, :block => e
|
#p :dummy => m, :pars => a, :block => e
|
||||||
|
@ -138,6 +172,9 @@ class SmqlToAR
|
||||||
where( where_str, @_wobs).
|
where( where_str, @_wobs).
|
||||||
order( @_order.join( ', ')).
|
order( @_order.join( ', ')).
|
||||||
includes( incls)
|
includes( incls)
|
||||||
|
@model = @model.limit @_limit if @_limit
|
||||||
|
@model = @model.offset @_offset if @_offset
|
||||||
|
@model
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_calculate
|
def fix_calculate
|
||||||
|
|
Loading…
Add table
Reference in a new issue