Compare commits
38 commits
Author | SHA1 | Date | |
---|---|---|---|
a4ce8a1e6b | |||
7f43e9fb86 | |||
3d922c5e47 | |||
7229fd185c | |||
08d1610987 | |||
ce81d21bd8 | |||
fbf5a7019c | |||
27639e24b7 | |||
40deac93bf | |||
6f8569f80a | |||
a4a1d1b9e9 | |||
f6b9579073 | |||
3ba285548c | |||
3d08496ec4 | |||
db1eec6fa7 | |||
edcc68a4ed | |||
2b3922e158 | |||
493556fda2 | |||
1c97319d9c | |||
293909b58b | |||
080237c5d7 | |||
1d6baf41e3 | |||
63be31734b | |||
69aceef180 | |||
8eebb3ac1f | |||
3b60648ee7 | |||
5c68b74ee4 | |||
f9ee0754f1 | |||
f3bcdd4b25 | |||
8eeb66d99f | |||
d5aa6052ec | |||
11466211ac | |||
cc49fdefe9 | |||
863ebca9cd | |||
09dfa782a8 | |||
75c314159b | |||
2fdc45d1d5 | |||
93fd3eeda4 |
1
Rakefile
1
Rakefile
|
@ -15,7 +15,6 @@ 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
TODO
2
TODO
|
@ -1 +1,3 @@
|
||||||
1) gem-plugin
|
1) gem-plugin
|
||||||
|
2) deprecated-warnings rails 3.1:
|
||||||
|
* DEPRECATION WARNING: primary_key_name is deprecated and will be removed from Rails 3.2 (use foreign_key instead).
|
||||||
|
|
|
@ -43,9 +43,9 @@ class SmqlToAR
|
||||||
# Die eigentliche Exception wird in @data[:exception] hinterlegt.
|
# Die eigentliche Exception wird in @data[:exception] hinterlegt.
|
||||||
class SubSMQLError < SMQLError
|
class SubSMQLError < SMQLError
|
||||||
def initialize query, model, exception
|
def initialize query, model, exception
|
||||||
ex = {:class => exception.class, :message => exception.message}
|
ex = {class: exception.class, message: exception.message}
|
||||||
ex[:data] = exception.data if exception.respond_to? :data
|
ex[:data] = exception.data if exception.respond_to? :data
|
||||||
super :query => query, :model => model.to_s, :exception => ex
|
super query: query, model: model.to_s, exception: ex
|
||||||
set_backtrace exception.backtrace
|
set_backtrace exception.backtrace
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -55,49 +55,49 @@ class SmqlToAR
|
||||||
# Malformed ColOp
|
# Malformed ColOp
|
||||||
class UnexpectedColOpError < ParseError
|
class UnexpectedColOpError < ParseError
|
||||||
def initialize model, colop, val
|
def initialize model, colop, val
|
||||||
super :got => colop, :val => val, :model => model.to_s
|
super got: colop, val: val, model: model.to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class UnexpectedError < ParseError
|
class UnexpectedError < ParseError
|
||||||
def initialize model, colop, val
|
def initialize model, colop, val
|
||||||
super :got => {colop => val}, :model => model.to_s
|
super got: {colop => val}, model: model.to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class NonExistingSelectableError < SMQLError
|
class NonExistingSelectableError < SMQLError
|
||||||
def initialize got
|
def initialize got
|
||||||
super :got => got
|
super got: got
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class NonExistingColumnError < SMQLError
|
class NonExistingColumnError < SMQLError
|
||||||
def initialize expected, got
|
def initialize expected, got
|
||||||
super :expected => expected, :got => got
|
super expected: expected, got: got
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class NonExistingRelationError < SMQLError
|
class NonExistingRelationError < SMQLError
|
||||||
def initialize expected, got
|
def initialize expected, got
|
||||||
super :expected => expected, :got => got
|
super expected: expected, got: got
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ProtectedColumnError < SMQLError
|
class ProtectedColumnError < SMQLError
|
||||||
def initialize protected_column
|
def initialize protected_column
|
||||||
super :protected_column => protected_column
|
super protected_column: protected_column
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class RootOnlyFunctionError < SMQLError
|
class RootOnlyFunctionError < SMQLError
|
||||||
def initialize path
|
def initialize path
|
||||||
super :path => path
|
super path: path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ConColumnError < SMQLError
|
class ConColumnError < SMQLError
|
||||||
def initialize expected, got
|
def initialize expected, got
|
||||||
super :expected => expected, :got => got
|
super expected: expected, got: got
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -107,7 +107,8 @@ class SmqlToAR
|
||||||
|
|
||||||
# Model der Relation `rel` von `model`
|
# Model der Relation `rel` von `model`
|
||||||
def self.model_of model, rel
|
def self.model_of model, rel
|
||||||
model.reflections[ rel.to_sym].andand.klass
|
r = model.reflections[ rel.to_sym].andand.klass
|
||||||
|
r.nil? && rel === :self ? model : r
|
||||||
end
|
end
|
||||||
|
|
||||||
# Eine Spalte in einer Tabelle, relativ zu `Column#model`.
|
# Eine Spalte in einer Tabelle, relativ zu `Column#model`.
|
||||||
|
@ -118,10 +119,43 @@ class SmqlToAR
|
||||||
attr_reader :path, :col
|
attr_reader :path, :col
|
||||||
attr_accessor :model
|
attr_accessor :model
|
||||||
|
|
||||||
|
class Col
|
||||||
|
include Comparable
|
||||||
|
attr_accessor :col, :as
|
||||||
|
def initialize col, as = nil
|
||||||
|
if col.kind_of? Col
|
||||||
|
@col, @as = col.col, col.as
|
||||||
|
elsif /^(.*?)\[(.*)\]$/ =~ col.to_s
|
||||||
|
@col, @as = $1, $2
|
||||||
|
else
|
||||||
|
@col, @as = col.to_s, as.to_s
|
||||||
|
@as = nil if @as.blank?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s() @col end
|
||||||
|
def to_sym() to_s.to_sym end
|
||||||
|
def inspect() "#{@col}[@#{@as}]" end
|
||||||
|
def to_alias() "#{@col}[#{@as}]" end
|
||||||
|
|
||||||
|
def == other
|
||||||
|
other = Col.new other
|
||||||
|
@col == other.col && @as == other.as
|
||||||
|
end
|
||||||
|
|
||||||
|
def === other
|
||||||
|
@col == Col.new( other).col
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def initialize model, *col
|
def initialize model, *col
|
||||||
@model = model
|
@model = model
|
||||||
@last_model = nil
|
@last_model = nil
|
||||||
*@path, @col = Array.wrap( col).collect( &it.to_s.split( /[.\/]/)).flatten.collect( &:to_sym)
|
*@path, @col = *Array.wrap( col).
|
||||||
|
collect {|x| x.to_s.split /[.\/]/ }.
|
||||||
|
flatten.
|
||||||
|
collect( &Col.method( :new)).
|
||||||
|
reject {|x| x === :self }
|
||||||
end
|
end
|
||||||
|
|
||||||
def last_model
|
def last_model
|
||||||
|
@ -131,10 +165,12 @@ class SmqlToAR
|
||||||
def each
|
def each
|
||||||
model = @model
|
model = @model
|
||||||
@path.each do |rel|
|
@path.each do |rel|
|
||||||
|
unless rel === :self
|
||||||
model = SmqlToAR.model_of model, rel
|
model = SmqlToAR.model_of model, rel
|
||||||
return false unless model
|
return false unless model
|
||||||
yield rel, model
|
yield rel, model
|
||||||
end
|
end
|
||||||
|
end
|
||||||
model
|
model
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -158,20 +194,22 @@ class SmqlToAR
|
||||||
def joins builder = nil, table = nil, &exe
|
def joins builder = nil, table = nil, &exe
|
||||||
pp = []
|
pp = []
|
||||||
table = Array.wrap table
|
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|
|
collect do |rel, model|
|
||||||
pp.push rel
|
pp.push rel
|
||||||
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 self?() !@col end
|
||||||
def to_a() @path+[@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_s() to_a.join '.' end
|
||||||
def to_sym() to_s.to_sym end
|
def to_sym() to_s.to_sym end
|
||||||
def to_json() to_s end
|
def to_json() to_s end
|
||||||
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() self.self? ? model : SmqlToAR.model_of( last_model, @col) end
|
||||||
def allowed?() ! self.protected? end
|
def allowed?() ! self.protected? end
|
||||||
def child?() @path.empty? and !!relation end
|
def child?() @path.empty? and !!relation end
|
||||||
end
|
end
|
||||||
|
@ -184,10 +222,8 @@ class SmqlToAR
|
||||||
SmqlToAR.logger = ::Rails.logger
|
SmqlToAR.logger = ::Rails.logger
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
|
||||||
require 'logger'
|
|
||||||
@@logger = Logger.new $stdout
|
|
||||||
end
|
end
|
||||||
|
@@logger = ::Rails.logger || begin require 'logger'; Logger.new( $stdout) end
|
||||||
|
|
||||||
class <<self
|
class <<self
|
||||||
def logger=(logger) @@logger = logger end
|
def logger=(logger) @@logger = logger end
|
||||||
|
@ -207,8 +243,8 @@ class SmqlToAR
|
||||||
refls = model.respond_to?( :reflections) && model.reflections
|
refls = model.respond_to?( :reflections) && model.reflections
|
||||||
refls && refls.each do |name, refl|
|
refls && refls.each do |name, refl|
|
||||||
r[model.name][name] = case 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::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}
|
when ActiveRecord::Reflection::AssociationReflection then {macro: refl.macro, model: refl.klass.name}
|
||||||
else raise "Ups: #{refl.class}"
|
else raise "Ups: #{refl.class}"
|
||||||
end
|
end
|
||||||
models.push refl.klass unless r.keys.include? refl.klass.name
|
models.push refl.klass unless r.keys.include? refl.klass.name
|
||||||
|
@ -218,38 +254,65 @@ class SmqlToAR
|
||||||
end
|
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
|
||||||
end
|
#end
|
||||||
#p conditions: @conditions
|
#p conditions: @conditions
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
def build prefix = nil, base_table = nil
|
def build
|
||||||
benchmark 'SMQL build query' do
|
#benchmark 'SMQL build query' do
|
||||||
@builder = QueryBuilder.new @model, prefix, base_table
|
@builder = QueryBuilder.new @model
|
||||||
table = @builder.base_table
|
table = @builder.base_table
|
||||||
@conditions.each &it.build( builder, table)
|
@conditions.each {|condition| condition.build builder, table }
|
||||||
end
|
#end
|
||||||
#p builder: @builder
|
#p builder: @builder
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
def ar
|
def ar
|
||||||
@ar ||= benchmark 'SMQL ar' do
|
@ar ||= #benchmark 'SMQL ar' do
|
||||||
@builder.to_ar
|
@builder.to_ar
|
||||||
end
|
#end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_ar
|
def to_ar
|
||||||
benchmark 'SMQL' do
|
#benchmark 'SMQL' do
|
||||||
parse
|
parse
|
||||||
build
|
build
|
||||||
ar
|
ar
|
||||||
end
|
#end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.to_ar *params
|
def self.to_ar *params
|
||||||
new( *params).to_ar
|
new( *params).to_ar
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.reload_library
|
||||||
|
lib_dir = File.dirname __FILE__
|
||||||
|
fj = lambda {|*a| File.join lib_dir, *a }
|
||||||
|
Object.send :remove_const, :SmqlToAR
|
||||||
|
load fj[ 'smql_to_ar.rb']
|
||||||
|
load fj[ 'smql_to_ar', 'condition_types.rb']
|
||||||
|
load fj[ 'smql_to_ar', 'query_builder.rb']
|
||||||
|
end
|
||||||
|
|
||||||
|
module ActiveRecordExtensions
|
||||||
|
def self.included base
|
||||||
|
base.extend ClassMethods
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def smql *params
|
||||||
|
SmqlToAR.to_ar self, *params
|
||||||
|
end
|
||||||
|
|
||||||
|
def smql_protected
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
ActiveRecord::Base.send :include, SmqlToAR::ActiveRecordExtensions
|
||||||
|
|
|
@ -14,6 +14,9 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# * Array als Typ ist zu allgemein. Allgemeine Lösung gesucht, um die Typen im Array angeben zu können.
|
||||||
|
|
||||||
class SmqlToAR
|
class SmqlToAR
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# Alle Subklassen (qualitativ: ConditionTypes::*), die als Superklasse Condition haben,
|
# Alle Subklassen (qualitativ: ConditionTypes::*), die als Superklasse Condition haben,
|
||||||
|
@ -33,16 +36,30 @@ class SmqlToAR
|
||||||
k.split( '|').collect &:to_sym
|
k.split( '|').collect &:to_sym
|
||||||
end
|
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.
|
# Eine Regel parsen.
|
||||||
# Ex: Person, "givenname=", "Peter"
|
# Ex: Person, "givenname=", "Peter"
|
||||||
def try_parse_it model, colop, val
|
def try_parse_it model, colop, val
|
||||||
r = nil
|
r = nil
|
||||||
#p :try_parse => { :model => model, :colop => colop, :value => val }
|
#p :try_parse => { :model => model, :colop => colop, :value => val }
|
||||||
constants.each do |c|
|
conditions.each do |c|
|
||||||
next if :Condition == c
|
raise_unless colop =~ /^(?:\d*:)?(.*?)((?:\W*(?!\])\W)?)$/, UnexpectedColOpError.new( model, colop, val)
|
||||||
c = const_get c
|
|
||||||
next if Condition === c
|
|
||||||
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
|
||||||
|
@ -81,14 +98,21 @@ class SmqlToAR
|
||||||
Expected = []
|
Expected = []
|
||||||
Where = nil
|
Where = nil
|
||||||
|
|
||||||
|
class <<self
|
||||||
# Versuche das Objekt zu erkennen. Operator und Expected muessen passen.
|
# Versuche das Objekt zu erkennen. Operator und Expected muessen passen.
|
||||||
# Passt das Object, die Klasse instanzieren.
|
# Passt das Object, die Klasse instanzieren.
|
||||||
def self.try_parse model, cols, op, val
|
def 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 :class => self, :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?( &it === val)
|
new model, cols, val if self::Operator === op and self::Expected.any? {|x| x === val}
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
"#{self.name}( :operator=>#{self::Operator.inspect}, :expected=>#{self::Expected.inspect}, :where=>#{self::Where.inspect})"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize model, cols, val
|
def initialize model, cols, val
|
||||||
|
#p init: self, caller: caller
|
||||||
@model, @cols = model, cols
|
@model, @cols = model, cols
|
||||||
@value = case val
|
@value = case val
|
||||||
when Hash, Range then val
|
when Hash, Range then val
|
||||||
|
@ -97,6 +121,10 @@ class SmqlToAR
|
||||||
verify
|
verify
|
||||||
end
|
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
|
def verify
|
||||||
@cols.each do |col|
|
@cols.each do |col|
|
||||||
verify_column col
|
verify_column col
|
||||||
|
@ -118,42 +146,44 @@ class SmqlToAR
|
||||||
end
|
end
|
||||||
|
|
||||||
# Erstelle alle noetigen Klauseln. builder nimmt diese entgegen,
|
# Erstelle alle noetigen Klauseln. builder nimmt diese entgegen,
|
||||||
# wobei builder.join, builder.select, builder.where und builder.wobs von interesse sind.
|
# 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.
|
# mehrere Schluessel bedeuten, dass die Values _alle_ zutreffen muessen, wobei die Schluessel geODERt werden.
|
||||||
# Ex:
|
# Ex:
|
||||||
# 1) {"givenname=", "Peter"} #=> givenname = 'Peter'
|
# 1) {"givenname=", "Peter"} #=> givenname = 'Peter'
|
||||||
# 2) {"givenname=", ["Peter", "Hans"]} #=> ( givenname = 'Peter' OR givenname = 'Hans' )
|
# 2) {"givenname=", ["Peter", "Hans"]} #=> ( givenname = 'Peter' OR givenname = 'Hans' )
|
||||||
# 3) {"givenname|surname=", ["Peter", "Mueller"]}
|
# 3) {"givenname|surname=", ["Peter", "Mueller"]}
|
||||||
# #=> ( givenname = 'Peter' OR surname = 'Peter' ) AND ( givenname = 'Mueller' OR surname = 'Mueller' )
|
# #=> ( givenname = 'Peter' OR surname = 'Peter' ) AND ( givenname = 'Mueller' OR surname = 'Mueller' )
|
||||||
def build builder, table
|
def condition_build builder, table
|
||||||
values = Hash[ @value.collect {|value| [ builder.vid, value ] } ]
|
values = Hash[ @value.collect {|value| [ builder.vid, value ] } ]
|
||||||
values.each {|k, v| builder.wobs k.sym => v }
|
values.each {|k, v| builder.wobs k.to_sym => v }
|
||||||
if 1 == @cols.length
|
if 1 == @cols.length
|
||||||
@cols.each do |col|
|
@cols.each do |col|
|
||||||
col.joins builder, table
|
col.joins builder, table
|
||||||
col = builder.column table+col.path, col.col
|
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
|
end
|
||||||
else
|
else
|
||||||
|
b2 = SmqlToAR::And.new builder
|
||||||
values.keys.each do |vid|
|
values.keys.each do |vid|
|
||||||
builder.where *@cols.collect {|col|
|
b2.where SmqlToAR::Or[ *@cols.collect {|col|
|
||||||
col.joins builder, table
|
col.joins builder, table
|
||||||
col = builder.column table+col.path, col.col
|
col = builder.column table+col.path, col.col
|
||||||
self.class::Where % [ col, vid.to_s ]
|
self.class::Where % [ col, vid.to_s ]
|
||||||
}
|
}]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
alias build condition_build
|
||||||
end
|
end
|
||||||
|
|
||||||
class NotInRange < Condition
|
class NotInRange < Condition
|
||||||
Operator = '!..'
|
Operator = '!..'
|
||||||
Where = "%s NOT BETWEEN %s AND %s"
|
Where = '%s NOT BETWEEN %s AND %s'
|
||||||
Expected = [Range, lambda {|val| Array === val && 2 == val.length } ]
|
Expected = [Range, lambda {|val| Array === val && 2 == val.length } ]
|
||||||
|
|
||||||
def initialze model, cols, val
|
def initialize model, cols, val
|
||||||
if Array === val && 2 == val.length
|
if Array === val
|
||||||
f, l = val
|
f, l = val
|
||||||
f, l = Time.parse(f), Time.parse(l) if f.kind_of? String
|
f, l = Time.parse(f), Time.parse(l) if f.kind_of? String
|
||||||
val = f..l
|
val = f..l
|
||||||
|
@ -161,23 +191,65 @@ class SmqlToAR
|
||||||
super model, cols, val
|
super model, cols, val
|
||||||
end
|
end
|
||||||
|
|
||||||
def build builder, table
|
def not_in_range_build builder, table
|
||||||
builder.wobs (v1 = builder.vid) => @value.begin, (v2 = builder.vid) => @value.end
|
builder.wobs (v1 = builder.vid).to_sym => @value.begin, (v2 = builder.vid).to_sym => @value.end
|
||||||
@cols.each do |col|
|
@cols.each do |col|
|
||||||
col.joins builder, table
|
col.joins builder, table
|
||||||
builder.where self.class::Where % [ builder.column( table+col.path, col.col), v1, v2]
|
builder.where self.class::Where % [ builder.column( table+col.path, col.col), v1, v2]
|
||||||
end
|
end
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
alias build not_in_range_build
|
||||||
end
|
end
|
||||||
InRange = simple_condition NotInRange, '..', "%s BETWEEN %s AND %s"
|
InRange = simple_condition NotInRange, '..', '%s BETWEEN %s AND %s'
|
||||||
|
|
||||||
|
# Every key-pair will be ORed. No multiple values possible.
|
||||||
|
class Overlaps < Condition
|
||||||
|
Operator, Where = '<=>', '(%s, %s) OVERLAPS (%s, %s)'
|
||||||
|
Expected = [Range, lambda {|val|
|
||||||
|
Array === val && 2 == val.length &&
|
||||||
|
[Time, Date, String].any? {|v|v===val[0]} &&
|
||||||
|
[Numeric, String].any? {|v|v===val[1]}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def initialize model, cols, val
|
||||||
|
if Array === val
|
||||||
|
f = Time.parse( val[0]).localtime
|
||||||
|
l = val[1]
|
||||||
|
l = case l
|
||||||
|
when String then Time.parse( l).localtime
|
||||||
|
when Numeric then f+l
|
||||||
|
else raise ArgumentError, "Unexpected type for end-value #{l.inspect}"
|
||||||
|
end
|
||||||
|
f += f.utc_offset
|
||||||
|
l += l.utc_offset
|
||||||
|
val = f.utc..l.utc
|
||||||
|
end
|
||||||
|
super model, cols, val
|
||||||
|
end
|
||||||
|
|
||||||
|
def overlaps_build builder, table
|
||||||
|
builder = Or.new builder
|
||||||
|
builder.wobs (v1 = builder.vid).to_sym => @value.begin, (v2 = builder.vid).to_sym => @value.end
|
||||||
|
v1 = "TIMESTAMP #{v1}"
|
||||||
|
v2 = "TIMESTAMP #{v2}"
|
||||||
|
@cols.each do |col|
|
||||||
|
col.joins builder, table
|
||||||
|
end.each_slice 2 do |f,s|
|
||||||
|
builder.where self.class::Where % [
|
||||||
|
builder.column( table+f.path, f.col), builder.column( table+s.path, s.col), v1, v2]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
alias build overlaps_build
|
||||||
|
end
|
||||||
|
NotOverlaps = simple_condition Overlaps, '<=>', 'NOT (%s, %s) OVERLAPS (%s, %s)'
|
||||||
|
|
||||||
class NotIn < Condition
|
class NotIn < Condition
|
||||||
Operator = '!|='
|
Operator = '!|='
|
||||||
Where = "%s NOT IN (%s)"
|
Where = "%s NOT IN (%s)"
|
||||||
Expected = [Array]
|
Expected = [Array]
|
||||||
|
|
||||||
def build builder, table
|
def not_in_build builder, table
|
||||||
builder.wobs (v = builder.vid).to_sym => @value
|
builder.wobs (v = builder.vid).to_sym => @value
|
||||||
@cols.each do |col|
|
@cols.each do |col|
|
||||||
col.joins builder, table
|
col.joins builder, table
|
||||||
|
@ -185,23 +257,37 @@ class SmqlToAR
|
||||||
end
|
end
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
alias build not_in_build
|
||||||
end
|
end
|
||||||
|
|
||||||
In = simple_condition NotIn, '|=', '%s IN (%s)', [Array]
|
In = simple_condition NotIn, '|=', '%s IN (%s)', [Array]
|
||||||
In2 = simple_condition In, '', nil, [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]
|
GreaterThanOrEqual = simple_condition Condition, '>=', "%s >= %s", [Array, Numeric]
|
||||||
LesserThanOrEqual = simple_condition Condition, '<=', "%s <= %s", [Array, Numeric]
|
LesserThanOrEqual = simple_condition Condition, '<=', "%s <= %s", [Array, Numeric]
|
||||||
|
class StringTimeGreaterThanOrEqual < Condition
|
||||||
|
Operator, Where, Expected = '>=', '%s >= %s', [Time, Date, String]
|
||||||
|
def initialize model, cols, val
|
||||||
|
super model, cols, Time.parse( val.to_s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
StringTimeLesserThanOrEqual = simple_condition StringTimeGreaterThanOrEqual, '<=', "%s <= %s"
|
||||||
|
|
||||||
|
# Examples:
|
||||||
|
# { 'articles=>' => { id: 1 } }
|
||||||
|
# { 'articles=>' => [ { id: 1 }, { id: 2 } ] }
|
||||||
class EqualJoin <Condition
|
class EqualJoin <Condition
|
||||||
Operator = '='
|
Operator = '=>'
|
||||||
Expected = [Hash]
|
Expected = [Hash, lambda {|x| x.kind_of?( Array) and x.all? {|y| y.kind_of?( Hash) }}]
|
||||||
|
|
||||||
def initialize *pars
|
def initialize *pars
|
||||||
super( *pars)
|
super( *pars)
|
||||||
|
@value = Array.wrap @value
|
||||||
cols = {}
|
cols = {}
|
||||||
@cols.each do |col|
|
@cols.each do |col|
|
||||||
col_model = col.relation
|
col_model = col.relation
|
||||||
cols[col] = [col_model] + ConditionTypes.try_parse( col_model, @value)
|
cols[col] = [col_model] + @value.collect {|val| ConditionTypes.try_parse( col_model, val) }
|
||||||
end
|
end
|
||||||
@cols = cols
|
@cols = cols
|
||||||
end
|
end
|
||||||
|
@ -210,15 +296,27 @@ class SmqlToAR
|
||||||
raise_unless col.relation, NonExistingRelationError.new( %w[Relation], col)
|
raise_unless col.relation, NonExistingRelationError.new( %w[Relation], col)
|
||||||
end
|
end
|
||||||
|
|
||||||
def build builder, table
|
def equal_join_build builder, table
|
||||||
|
if 2 < @cols.first.second.length
|
||||||
|
b2, b3 = And, Or
|
||||||
|
else
|
||||||
|
b2, b3 = Or, And
|
||||||
|
end
|
||||||
|
b2 = b2.new builder
|
||||||
@cols.each do |col, sub|
|
@cols.each do |col, sub|
|
||||||
|
model, *sub = sub
|
||||||
t = table + col.path + [col.col]
|
t = table + col.path + [col.col]
|
||||||
col.joins.each {|j, m| builder.join table+j, m }
|
col.joins builder, table
|
||||||
builder.join t, sub[0]
|
builder.joins t, model
|
||||||
sub[1..-1].each &it.build( builder, t)
|
b4 = b3.new( b2)
|
||||||
|
sub.each do |i|
|
||||||
|
b5 = And.new b4
|
||||||
|
i.collect {|j| j.build b5, t }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
alias build equal_join_build
|
||||||
end
|
end
|
||||||
|
|
||||||
# Takes to Queries.
|
# Takes to Queries.
|
||||||
|
@ -233,6 +331,7 @@ class SmqlToAR
|
||||||
# is, second is not allowed (limit and order must be in root) and this means something like
|
# 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.
|
# "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."
|
# limit and order has no function in this query and this article needn't to be the last."
|
||||||
|
=begin
|
||||||
class SubEqualJoin < EqualJoin
|
class SubEqualJoin < EqualJoin
|
||||||
Operator = '()'
|
Operator = '()'
|
||||||
Expected = [lambda {|x| x.kind_of?( Array) and (1..2).include?( x.length) and x.all?( &it.kind_of?( Hash))}]
|
Expected = [lambda {|x| x.kind_of?( Array) and (1..2).include?( x.length) and x.all?( &it.kind_of?( Hash))}]
|
||||||
|
@ -240,32 +339,37 @@ class SmqlToAR
|
||||||
def initialize model, cols, val
|
def initialize model, cols, val
|
||||||
super model, cols, val[1]
|
super model, cols, val[1]
|
||||||
# sub: model, subquery, sub(condition)
|
# sub: model, subquery, sub(condition)
|
||||||
@cols.each {|col, sub| sub[ 1...1] = SmqlToAR.new( col.relation, val[0]).parse }
|
@cols.each {|col, sub| sub[ 1..-1] = SmqlToAR.new( col.relation, val[0]).parse, *sub[-1] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def verify_column col
|
def verify_column col
|
||||||
raise_unless col.child?, ConColumnError.new( [:Column], col)
|
raise_unless col.child?, ConColumnError.new( [:Column], col)
|
||||||
end
|
end
|
||||||
|
|
||||||
def build builder, table
|
def sub_equal_join_build builder, table
|
||||||
@cols.each do |col, sub|
|
@cols.each do |col, sub|
|
||||||
t = table+col.to_a
|
t = table+col.to_a
|
||||||
p t: t, sub: sub
|
builder.sub_joins t, col, *sub[0..1]
|
||||||
builder.sub_join t, col, *sub[0..1]
|
#ap sub: sub[2..-1]
|
||||||
sub[2..-1].each &it.build( builder, t)
|
sub[2..-1].each {|x| x.build builder, t }
|
||||||
end
|
end
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
alias build sub_equal_join_build
|
||||||
end
|
end
|
||||||
|
=end
|
||||||
|
|
||||||
Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric]
|
Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric, Date, Time]
|
||||||
Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric]
|
Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric, Date, Time]
|
||||||
GreaterThan = simple_condition Condition, '>', "%s > %s", [Array, Numeric]
|
GreaterThan = simple_condition Condition, '>', "%s > %s", [Array, Numeric]
|
||||||
|
StringTimeGreaterThan = simple_condition StringTimeGreaterThanOrEqual, '>', "%s > %s"
|
||||||
LesserThan = simple_condition Condition, '<', "%s < %s", [Array, Numeric]
|
LesserThan = simple_condition Condition, '<', "%s < %s", [Array, Numeric]
|
||||||
|
StringTimeLesserThan = simple_condition StringTimeGreaterThanOrEqual, '<', "%s < %s"
|
||||||
NotIlike = simple_condition Condition, '!~', "%s NOT ILIKE %s", [Array, String]
|
NotIlike = simple_condition Condition, '!~', "%s NOT ILIKE %s", [Array, String]
|
||||||
Ilike = simple_condition Condition, '~', "%s 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]
|
Join = simple_condition EqualJoin, '', nil, [Hash]
|
||||||
InRange2 = simple_condition InRange, '', nil, [Range]
|
InRange2 = simple_condition InRange, '', nil, [Range]
|
||||||
class Select < Condition
|
class Select < Condition
|
||||||
|
@ -276,7 +380,7 @@ class SmqlToAR
|
||||||
raise_unless col.exist_in? || SmqlToAR.model_of( col.last_model, col.col), NonExistingSelectableError.new( col)
|
raise_unless col.exist_in? || SmqlToAR.model_of( col.last_model, col.col), NonExistingSelectableError.new( col)
|
||||||
end
|
end
|
||||||
|
|
||||||
def build builder, table
|
def select_build builder, table
|
||||||
@cols.each do |col|
|
@cols.each do |col|
|
||||||
if col.exist_in?
|
if col.exist_in?
|
||||||
col.joins builder, table
|
col.joins builder, table
|
||||||
|
@ -288,6 +392,7 @@ class SmqlToAR
|
||||||
end
|
end
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
alias build select_build
|
||||||
end
|
end
|
||||||
|
|
||||||
class Functions < Condition
|
class Functions < Condition
|
||||||
|
@ -300,9 +405,14 @@ class SmqlToAR
|
||||||
Expected = []
|
Expected = []
|
||||||
attr_reader :model, :func, :args
|
attr_reader :model, :func, :args
|
||||||
|
|
||||||
def self.try_parse model, func, args
|
class <<self
|
||||||
SmqlToAR.logger.info( { try_parse: [func,args]}.inspect)
|
def try_parse model, func, args
|
||||||
self.new model, func, args if self::Name === func and self::Expected.any?( &it === args)
|
self.new model, func, args if self::Name === func and self::Expected.any? {|x| x === args }
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
"#{self.name}( :name=>#{self::Name}, :expected=>#{self::Expected})"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize model, func, args
|
def initialize model, func, args
|
||||||
|
@ -331,40 +441,42 @@ class SmqlToAR
|
||||||
super model, func, args
|
super model, func, args
|
||||||
end
|
end
|
||||||
|
|
||||||
def build builder, table
|
def order_build builder, table
|
||||||
return if @args.blank?
|
return if @args.blank?
|
||||||
@args.each do |o|
|
@args.each do |o|
|
||||||
col, o = o
|
col, o = o
|
||||||
col.joins builder, table
|
col.joins builder, table
|
||||||
t = table + col.path
|
t = table + col.path
|
||||||
raise_unless 1 == t.length, RootOnlyFunctionError.new( t)
|
#raise_unless 1 == t.length, RootOnlyFunctionError.new( t)
|
||||||
builder.order t, col.col, o
|
builder.order t, col.col, o
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
alias build order_build
|
||||||
end
|
end
|
||||||
|
|
||||||
class Limit < Function
|
class Limit < Function
|
||||||
Name = :limit
|
Name = :limit
|
||||||
Expected = [Fixnum]
|
Expected = [Fixnum]
|
||||||
|
|
||||||
def build builder, table
|
def limit_build builder, table
|
||||||
raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
|
raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
|
||||||
builder.limit @args
|
builder.limit = Array.wrap(@args).first.to_i
|
||||||
end
|
end
|
||||||
|
alias build limit_build
|
||||||
end
|
end
|
||||||
|
|
||||||
class Offset < Function
|
class Offset < Function
|
||||||
Name = :offset
|
Name = :offset
|
||||||
Expected = [Fixnum]
|
Expected = [Fixnum]
|
||||||
|
|
||||||
def build builder, table
|
def offset_build builder, table
|
||||||
raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
|
raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
|
||||||
builder.offset @args
|
builder.offset = Array.wrap(@args).first.to_i
|
||||||
end
|
end
|
||||||
|
alias build offset_build
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.new model, col, val
|
def self.new model, col, val
|
||||||
SmqlToAR.logger.info( { function: col.first.to_sym }.inspect)
|
|
||||||
r = nil
|
r = nil
|
||||||
constants.each do |c|
|
constants.each do |c|
|
||||||
next if [:Function, :Where, :Expected, :Operator].include? c
|
next if [:Function, :Where, :Expected, :Operator].include? c
|
||||||
|
|
|
@ -26,33 +26,70 @@ class SmqlToAR
|
||||||
def to_sym() "smql_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
|
||||||
|
def === other
|
||||||
|
to_s === other || to_sym === other || to_i === other || self == other || self === other
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Aliases < Hash
|
||||||
|
attr_accessor :counter, :prefix
|
||||||
|
|
||||||
|
def initialize prefix, *a, &e
|
||||||
|
@counter, @prefix = 0, prefix || 'smql'
|
||||||
|
super *a, &e
|
||||||
|
end
|
||||||
|
|
||||||
|
def format name
|
||||||
|
pre, suf = name.split( ',', 2)
|
||||||
|
return name unless suf
|
||||||
|
pre += ",#{@counter += 1},"
|
||||||
|
l = 60-pre.length
|
||||||
|
n = suf[(suf.length<=l ? 0 : -l)..-1]
|
||||||
|
n == suf ? pre+n : "#{pre},,,#{n}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def name n
|
||||||
|
n.collect( &:to_alias).join ','
|
||||||
|
end
|
||||||
|
|
||||||
|
def [] k
|
||||||
|
n = name k
|
||||||
|
v = super n
|
||||||
|
v = self[k] = format( "#{prefix},#{n}") unless v
|
||||||
|
v
|
||||||
|
end
|
||||||
|
|
||||||
|
def []= k, v
|
||||||
|
super name( k), v
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins, :prefix, :_vid
|
attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins, :prefix, :_vid
|
||||||
attr_accessor :logger
|
attr_accessor :logger, :limit, :offset
|
||||||
|
|
||||||
def initialize model, prefix = nil, base_table
|
def initialize model, prefix = nil
|
||||||
@prefix = "smql"
|
@prefix = "smql"
|
||||||
@logger = SmqlToAR.logger
|
@logger = SmqlToAR.logger
|
||||||
@table_alias = Hash.new do |h, k|
|
@table_alias = Aliases.new @prefix
|
||||||
k = Array.wrap k
|
@_vid, @_where, @_wobs, @model, @quote = 0, SmqlToAR::And[], {}, model, model.connection
|
||||||
h[k] = "#{@prefix},#{k.join(',')}"
|
@base_table = [Column::Col.new( model.table_name)]
|
||||||
end
|
|
||||||
@_vid, @_where, @_wobs, @model, @quoter = 0, [], {}, model, model.connection
|
|
||||||
@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 @base_table.first.col
|
||||||
@_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [], [], []
|
@_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [@base_table], [], []
|
||||||
@table_model = {@base_table => @model}
|
@table_model = {@base_table => @model}
|
||||||
end
|
end
|
||||||
|
|
||||||
def vid() Vid.new( @_vid+=1) 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.
|
# Jede via where uebergebene Condition wird geodert und alle zusammen werden geundet.
|
||||||
# "Konjunktive Normalform". Allerdings duerfen Conditions auch Komplexe Abfragen enthalten.
|
# "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 )
|
# #=> 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
|
@_where.push cond
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
@ -63,64 +100,66 @@ class SmqlToAR
|
||||||
end
|
end
|
||||||
|
|
||||||
def quote_column_name name
|
def quote_column_name name
|
||||||
@quoter.quote_column_name( name).gsub /"\."/, ','
|
@quote.quote_column_name( name).gsub /"\."/, ','
|
||||||
end
|
end
|
||||||
|
|
||||||
def quote_table_name name
|
def quote_table_name name
|
||||||
@quoter.quote_table_name( name).gsub /"\."/, ','
|
name = case name
|
||||||
|
when Array, Column::Col then @table_alias[Array.wrap name]
|
||||||
|
else name.to_s
|
||||||
|
end
|
||||||
|
@quote.quote_table_name name
|
||||||
end
|
end
|
||||||
|
|
||||||
def column table, name
|
def column table, name
|
||||||
"#{quote_table_name table.kind_of?(String) ? table : @table_alias[table]}.#{quote_column_name name}"
|
"#{quote_table_name table}.#{quote_column_name name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_join orig, pretable, table, prekey, key
|
def build_join orig, pretable, table, prekey, key
|
||||||
" JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} "
|
"\tLEFT JOIN #{orig} AS #{quote_table_name table}\n\tON #{column pretable, prekey} = #{column table, key}\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
def sub_join table, col, model, query
|
def sub_joins table, col, model, query
|
||||||
pp [:sub_join, table, col. model, query]
|
prefix, base_table = "#{@prefix}_sub", col.relation.table_name
|
||||||
prefix, base_table = "#{@prefix}_sub", col.col
|
join_ table, model, "(#{query.build( prefix).ar.to_sql})"
|
||||||
join_ table, model, "(#{query.build( prefix, base_table).tap{|q| p :sub_join => q }.ar.to_sql})"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def join_ table, model, query, pretable = nil
|
def join_ table, model, query, pretable = nil
|
||||||
pp [:join_, table, model, query]
|
|
||||||
pretable ||= table[0...-1]
|
pretable ||= table[0...-1]
|
||||||
@table_model[ table] = model
|
@table_model[ table] = model
|
||||||
premodel = @table_model[ pretable]
|
@table_model.rehash
|
||||||
|
premodel = @table_model.find {|k,v| pretable == k }[1]
|
||||||
t = @table_alias[ table]
|
t = @table_alias[ table]
|
||||||
pt = quote_table_name @table_alias[ table[ 0...-1]]
|
pt = quote_table_name table[ 0...-1]
|
||||||
pp premodel: premodel, table: table
|
refl = premodel.reflections[table.last.to_sym]
|
||||||
refl = premodel.reflections[table.last]
|
|
||||||
case refl
|
case refl
|
||||||
when ActiveRecord::Reflection::ThroughReflection
|
when ActiveRecord::Reflection::ThroughReflection
|
||||||
through = refl.through_reflection
|
through = refl.through_reflection
|
||||||
pp refl: refl
|
through_table = table[0...-1]+[Column::Col.new( through.name, table.last.as)]
|
||||||
throughtable = table[0...-1]+[through.name.to_sym]
|
srctable = through_table+[Column::Col.new( refl.source_reflection.name, table.last.as)]
|
||||||
srctable = throughtable+[refl.source_reflection.name]
|
|
||||||
@table_model[ srctable] = model
|
@table_model[ srctable] = model
|
||||||
@table_alias[ table] = @table_alias[ srctable]
|
@table_alias[ table] = @table_alias[ srctable]
|
||||||
join_ throughtable, through.klass, quote_table_name( through.table_name)
|
join_ through_table, through.klass, quote_table_name( through.table_name)
|
||||||
join_ srctable, refl.klass, query, throughtable
|
join_ srctable, refl.klass, query, through_table
|
||||||
when ActiveRecord::Reflection::AssociationReflection
|
when ActiveRecord::Reflection::AssociationReflection
|
||||||
case refl.macro
|
case refl.macro
|
||||||
when :has_many
|
when :has_many, :has_one
|
||||||
@_joins += build_join query, pretable, t, premodel.primary_key, refl.primary_key_name
|
@_joins += build_join query, pretable, t, premodel.primary_key, refl.foreign_key
|
||||||
when :belongs_to
|
when :belongs_to
|
||||||
@_joins += build_join query, pretable, t, refl.primary_key_name, premodel.primary_key
|
@_joins += build_join query, pretable, t, refl.foreign_key, premodel.primary_key
|
||||||
when :has_and_belongs_to_many
|
when :has_and_belongs_to_many
|
||||||
jointable = [','] + table
|
jointable = [Column::Col.new(',')] + table
|
||||||
@_joins += build_join refl.options[:join_table], pretable, @table_alias[jointable], premodel.primary_key, refl.primary_key_name
|
@_joins += build_join refl.options[:join_table], pretable, @table_alias[ jointable], premodel.primary_key, refl.foreign_key
|
||||||
@_joins += build_join query, jointable, t, refl.association_foreign_key, refl.association_primary_key
|
@_joins += build_join query, jointable, t, refl.association_foreign_key, refl.association_primary_key
|
||||||
else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}"
|
else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}"
|
||||||
end
|
end
|
||||||
else raise BuilderError, "Unkown reflection type: #{refl.class.name}"
|
else raise BuilderError, "Unkown reflection type: #{refl.class.name} #{refl.macro.inspect}"
|
||||||
end
|
end
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
def join table, model
|
def joins table, model
|
||||||
|
table = table.flatten.compact
|
||||||
return self if @_joined.include? table # Already joined
|
return self if @_joined.include? table # Already joined
|
||||||
join_ table, model, quote_table_name( model.table_name)
|
join_ table, model, quote_table_name( model.table_name)
|
||||||
@_joined.push table
|
@_joined.push table
|
||||||
|
@ -137,43 +176,27 @@ class SmqlToAR
|
||||||
end
|
end
|
||||||
|
|
||||||
def order table, col, o
|
def order table, col, o
|
||||||
@_order.push "#{column table, col} #{:DESC == o ? :DESC : :ASC}"
|
ct = column table, col
|
||||||
end
|
@_select.push ct
|
||||||
|
@_order.push "#{ct} #{:DESC == o ? :DESC : :ASC}"
|
||||||
def limit count
|
|
||||||
@_limit = count
|
|
||||||
end
|
|
||||||
|
|
||||||
def offset count
|
|
||||||
@_offset = count
|
|
||||||
end
|
|
||||||
|
|
||||||
class Dummy
|
|
||||||
def method_missing m, *a, &e
|
|
||||||
#p :dummy => m, :pars => a, :block => e
|
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def build_ar
|
def build_ar
|
||||||
where_str = @_where.collect do |w|
|
where_str = @_where.type_correction!.optimize!.build_where
|
||||||
w = Array.wrap w
|
|
||||||
1 == w.length ? w.first : "( #{w.join( ' OR ')} )"
|
|
||||||
end.join ' AND '
|
|
||||||
incls = {}
|
incls = {}
|
||||||
@_includes.each do |inc|
|
@_includes.each do |inc|
|
||||||
b = incls
|
b = incls
|
||||||
inc[1..-1].collect {|rel| b = b[rel] ||= {} }
|
inc[1..-1].collect {|rel| b = b[rel] ||= {} }
|
||||||
end
|
end
|
||||||
@logger.debug incls: incls, joins: @_joins
|
|
||||||
@model = @model.
|
@model = @model.
|
||||||
select( @_select.join( ', ')).
|
select( @_select.join( ', ')).
|
||||||
joins( @_joins).
|
joins( @_joins).
|
||||||
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.limit @limit if @limit
|
||||||
@model = @model.offset @_offset if @_offset
|
@model = @model.offset @offset if @offset
|
||||||
@model
|
@model
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -193,4 +216,75 @@ class SmqlToAR
|
||||||
@model
|
@model
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class SubBuilder < Array
|
||||||
|
attr_reader :parent, :_where
|
||||||
|
delegate :wobs, :joins, :includes, :sub_joins, :vid, :quote_column_name, :quote, :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? SubBuilder
|
||||||
|
if self.class == sub.class
|
||||||
|
ext.push *sub
|
||||||
|
nil
|
||||||
|
elsif sub.blank?
|
||||||
|
nil
|
||||||
|
elsif 1 == sub.size
|
||||||
|
sub.first
|
||||||
|
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 indent = nil
|
||||||
|
indent = (indent||0) + 1
|
||||||
|
collect {|x| "(#{x.respond_to?( :build_where) ? x.build_where( indent) : x.to_s})" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class And < SubBuilder
|
||||||
|
def default; SmqlToAR::Or; end
|
||||||
|
def build_where indent = nil
|
||||||
|
collect_build_where( indent).join " AND\n"+"\t"*(indent||0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Or < SubBuilder
|
||||||
|
def build_where indent = nil
|
||||||
|
collect_build_where( indent).join " OR\n"+"\t"*(indent||0)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue