diff --git a/.gitignore b/.gitignore
index 1b861d7..73633ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,2 @@
pkg/**
-.*.swp
smql.gemspec
diff --git a/TODO b/TODO
index e2a2eb8..28c5c5c 100644
--- a/TODO
+++ b/TODO
@@ -1,3 +1 @@
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).
diff --git a/VERSION b/VERSION
index 5a5831a..4e379d2 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.0.7
+0.0.2
diff --git a/lib/smql_to_ar.rb b/lib/smql_to_ar.rb
index 702c554..63e1a10 100644
--- a/lib/smql_to_ar.rb
+++ b/lib/smql_to_ar.rb
@@ -15,18 +15,6 @@
# 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
@@ -43,9 +31,9 @@ class SmqlToAR
# Die eigentliche Exception wird in @data[:exception] hinterlegt.
class SubSMQLError < SMQLError
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
- super query: query, model: model.to_s, exception: ex
+ super :query => query, :model => model.to_s, :exception => ex
set_backtrace exception.backtrace
end
end
@@ -55,49 +43,43 @@ class SmqlToAR
# Malformed ColOp
class UnexpectedColOpError < ParseError
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
class UnexpectedError < ParseError
def initialize model, colop, val
- super got: {colop => val}, model: model.to_s
+ super :got => {colop => val}, :model => model.to_s
end
end
class NonExistingSelectableError < SMQLError
def initialize got
- super got: got
+ super :got => got
end
end
class NonExistingColumnError < SMQLError
def initialize expected, got
- super expected: expected, got: got
+ super :expected => expected, :got => got
end
end
class NonExistingRelationError < SMQLError
def initialize expected, got
- super expected: expected, got: got
+ super :expected => expected, :got => got
end
end
class ProtectedColumnError < SMQLError
def initialize protected_column
- super protected_column: protected_column
+ super :protected_column => protected_column
end
end
- class RootOnlyFunctionError < SMQLError
+ class OnlyOrderOnBaseError < SMQLError
def initialize path
- super path: path
- end
- end
-
- class ConColumnError < SMQLError
- def initialize expected, got
- super expected: expected, got: got
+ super :path => path
end
end
@@ -107,8 +89,7 @@ class SmqlToAR
# Model der Relation `rel` von `model`
def self.model_of model, rel
- r = model.reflections[ rel.to_sym].andand.klass
- r.nil? && rel === :self ? model : r
+ model.reflections[ rel.to_sym].andand.klass
end
# Eine Spalte in einer Tabelle, relativ zu `Column#model`.
@@ -119,43 +100,10 @@ class SmqlToAR
attr_reader :path, :col
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
@model = model
@last_model = nil
- *@path, @col = *Array.wrap( col).
- collect {|x| x.to_s.split /[.\/]/ }.
- flatten.
- collect( &Col.method( :new)).
- reject {|x| x === :self }
+ *@path, @col = Array.wrap( col).collect {|s| s.to_s.split /[.\/]/ }.flatten.collect &:to_sym
end
def last_model
@@ -165,11 +113,9 @@ class SmqlToAR
def each
model = @model
@path.each do |rel|
- unless rel === :self
- model = SmqlToAR.model_of model, rel
- return false unless model
- yield rel, model
- end
+ model = SmqlToAR.model_of model, rel
+ return false unless model
+ yield rel, model
end
model
end
@@ -194,24 +140,19 @@ class SmqlToAR
def joins builder = nil, table = nil, &exe
pp = []
table = Array.wrap table
- exe ||= builder ? lambda {|j, m| builder.joins table+j, m} : Array.method( :[])
+ exe ||= builder ? lambda {|j, m| builder.join table+j, m} : Array.method( :[])
collect do |rel, model|
pp.push rel
exe.call pp, model
end
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_a() @path+[@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() self.self? ? model : SmqlToAR.model_of( last_model, @col) end
+ def relation() 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
@@ -222,8 +163,10 @@ class SmqlToAR
SmqlToAR.logger = ::Rails.logger
end
end
+ else
+ require 'logger'
+ @@logger = Logger.new $stdout
end
- @@logger = ::Rails.logger || begin require 'logger'; Logger.new( $stdout) end
class <.
-# TODO:
-# * Array als Typ ist zu allgemein. Allgemeine Lösung gesucht, um die Typen im Array angeben zu können.
-
class SmqlToAR
#############################################################################
# Alle Subklassen (qualitativ: ConditionTypes::*), die als Superklasse Condition haben,
@@ -28,44 +25,28 @@ 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 }
- conditions.each do |c|
- raise_unless colop =~ /^(?:\d*:)?(.*?)((?:\W*(?!\])\W)?)$/, UnexpectedColOpError.new( model, colop, 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*)$/
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_unless r, UnexpectedError.new( model, colop, val)
+ raise UnexpectedError.new( model, colop, val) unless r
r
end
@@ -91,28 +72,19 @@ class SmqlToAR
end
class Condition
- include SmqlToAR::Assertion
- extend SmqlToAR::Assertion
attr_reader :value, :cols
Operator = nil
Expected = []
Where = nil
- 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? {|x| x === val}
- end
-
- def inspect
- "#{self.name}( :operator=>#{self::Operator.inspect}, :expected=>#{self::Expected.inspect}, :where=>#{self::Where.inspect})"
- end
+ # 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 }
end
def initialize model, cols, val
- #p init: self, caller: caller
@model, @cols = model, cols
@value = case val
when Hash, Range then val
@@ -121,10 +93,6 @@ 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
@@ -135,55 +103,53 @@ class SmqlToAR
# Gibt es eine Spalte diesen Namens?
# Oder: Gibt es eine Relation diesen Namens? (Hier nicht der Fall)
def verify_column col
- raise_unless col.exist_in?, NonExistingColumnError.new( %w[Column], col)
+ raise NonExistingColumnError.new( %w[Column], col) unless col.exist_in?
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_if col.protected?, ProtectedColumnError.new( col)
+ raise ProtectedColumnError.new( col) if col.protected?
end
# Erstelle alle noetigen Klauseln. builder nimmt diese entgegen,
- # 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.
+ # 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.
# Ex:
# 1) {"givenname=", "Peter"} #=> givenname = 'Peter'
# 2) {"givenname=", ["Peter", "Hans"]} #=> ( givenname = 'Peter' OR givenname = 'Hans' )
# 3) {"givenname|surname=", ["Peter", "Mueller"]}
# #=> ( givenname = 'Peter' OR surname = 'Peter' ) AND ( givenname = 'Mueller' OR surname = 'Mueller' )
- def condition_build builder, table
+ def build builder, table
values = Hash[ @value.collect {|value| [ builder.vid, value ] } ]
- values.each {|k, v| builder.wobs k.to_sym => v }
+ values.each {|k, v| builder.wobs k.sym => v }
if 1 == @cols.length
@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|
- b2.where SmqlToAR::Or[ *@cols.collect {|col|
+ builder.where *@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
end
- alias build condition_build
end
class NotInRange < Condition
Operator = '!..'
- Where = '%s NOT BETWEEN %s AND %s'
+ Where = "%s NOT BETWEEN %s AND %s"
Expected = [Range, lambda {|val| Array === val && 2 == val.length } ]
- def initialize model, cols, val
- if Array === val
+ def initialze model, cols, val
+ if Array === val && 2 == val.length
f, l = val
f, l = Time.parse(f), Time.parse(l) if f.kind_of? String
val = f..l
@@ -191,65 +157,23 @@ class SmqlToAR
super model, cols, val
end
- def not_in_range_build builder, table
- builder.wobs (v1 = builder.vid).to_sym => @value.begin, (v2 = builder.vid).to_sym => @value.end
+ def build builder, table
+ builder.wobs (v1 = builder.vid) => @value.begin, (v2 = builder.vid) => @value.end
@cols.each do |col|
col.joins builder, table
builder.where self.class::Where % [ builder.column( table+col.path, col.col), v1, v2]
end
self
end
- alias build not_in_range_build
end
- 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)'
+ InRange = simple_condition NotInRange, '..', "%s BETWEEN %s AND %s"
class NotIn < Condition
Operator = '!|='
Where = "%s NOT IN (%s)"
Expected = [Array]
- def not_in_build builder, table
+ def build builder, table
builder.wobs (v = builder.vid).to_sym => @value
@cols.each do |col|
col.joins builder, table
@@ -257,119 +181,54 @@ class SmqlToAR
end
self
end
- alias build not_in_build
end
In = simple_condition NotIn, '|=', '%s IN (%s)', [Array]
In2 = simple_condition In, '', nil, [Array]
- NotEqual = simple_condition Condition, '!=', "%s <> %s", [Array, String, Numeric]
- NotEqual2 = simple_condition Condition, '<>', "%s <> %s", [Array, String, Numeric]
+ NotEqual = simple_condition Condition, /\!=|<>/, "%s <> %s", [Array, String, Numeric]
GreaterThanOrEqual = 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 @model.reflections.keys
+ raise NonExistingRelationError.new( %w[Relation], col) unless refl
end
- 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
+ def build builder, table
@cols.each do |col, sub|
- model, *sub = sub
t = table + col.path + [col.col]
- col.joins builder, table
- builder.joins t, model
- b4 = b3.new( b2)
- sub.each do |i|
- b5 = And.new b4
- i.collect {|j| j.build b5, t }
- end
+ #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 }
end
self
end
- alias build equal_join_build
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 sub_equal_join_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 {|x| x.build builder, t }
- end
- self
- end
- alias build sub_equal_join_build
- end
-=end
-
- Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric, Date, Time]
- Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric, Date, Time]
+ 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]
- StringTimeGreaterThan = simple_condition StringTimeGreaterThanOrEqual, '>', "%s > %s"
LesserThan = simple_condition Condition, '<', "%s < %s", [Array, Numeric]
- StringTimeLesserThan = simple_condition StringTimeGreaterThanOrEqual, '<', "%s < %s"
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
@@ -377,10 +236,10 @@ class SmqlToAR
Expected = [nil]
def verify_column col
- raise_unless col.exist_in? || SmqlToAR.model_of( col.last_model, col.col), NonExistingSelectableError.new( col)
+ raise NonExistingSelectableError.new( col) unless col.exist_in? or SmqlToAR.model_of( col.last_model, col.col)
end
- def select_build builder, table
+ def build builder, table
@cols.each do |col|
if col.exist_in?
col.joins builder, table
@@ -392,7 +251,6 @@ class SmqlToAR
end
self
end
- alias build select_build
end
class Functions < Condition
@@ -400,19 +258,13 @@ class SmqlToAR
Expected = [String, Array, Hash, Numeric, nil]
class Function
- include SmqlToAR::Assertion
Name = nil
Expected = []
attr_reader :model, :func, :args
- class <#{self::Name}, :expected=>#{self::Expected})"
- end
+ 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 }
end
def initialize model, func, args
@@ -425,64 +277,47 @@ 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_unless col.exist_in?, NonExistingColumnError.new( [:Column], col)
+ raise NonExistingColumnError.new( [:Column], col) unless col.exist_in?
[col, o]
end
+ SmqlToAR.logger.info( {args: args}.inspect)
super model, func, args
end
- def order_build builder, table
+ def build builder, table
return if @args.blank?
@args.each do |o|
col, o = o
col.joins builder, table
t = table + col.path
- #raise_unless 1 == t.length, RootOnlyFunctionError.new( t)
+ #raise OnlyOrderOnBaseError.new( t) unless 1 == t.length
builder.order t, col.col, o
end
end
- alias build order_build
- end
-
- class Limit < Function
- Name = :limit
- Expected = [Fixnum]
-
- def limit_build builder, table
- raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
- builder.limit = Array.wrap(@args).first.to_i
- end
- alias build limit_build
- end
-
- class Offset < Function
- Name = :offset
- Expected = [Fixnum]
-
- def offset_build builder, table
- raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
- builder.offset = Array.wrap(@args).first.to_i
- end
- alias build offset_build
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 4e8ad3c..0eb06a5 100644
--- a/lib/smql_to_ar/query_builder.rb
+++ b/lib/smql_to_ar/query_builder.rb
@@ -22,74 +22,36 @@ class SmqlToAR
class Vid
attr_reader :vid
def initialize( vid) @vid = vid end
- def to_s() ":smql_c#{@vid}" end
- def to_sym() "smql_c#{@vid}".to_sym end
+ def to_s() ":c#{@vid}" end
+ def to_sym() "c#{@vid}".to_sym end
alias sym to_sym
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
+ attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins
+ attr_accessor :logger
- 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
-
- attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins, :prefix, :_vid
- attr_accessor :logger, :limit, :offset
-
- def initialize model, prefix = nil
- @prefix = "smql"
+ def initialize model
@logger = SmqlToAR.logger
- @table_alias = Aliases.new @prefix
- @_vid, @_where, @_wobs, @model, @quote = 0, SmqlToAR::And[], {}, model, model.connection
- @base_table = [Column::Col.new( model.table_name)]
+ @table_alias = Hash.new do |h, k|
+ k = Array.wrap k
+ h[k] = "smql,#{k.join(',')}"
+ end
+ @_vid, @_where, @_wobs, @model, @quoter = 0, [], {}, model, model.connection
+ @base_table = [model.table_name.to_sym]
@table_alias[ @base_table] = @base_table.first
- t = quote_table_name @base_table.first.col
- @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [@base_table], [], []
+ t = quote_table_name @table_alias[ @base_table]
+ @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [], [], []
@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
@@ -100,69 +62,42 @@ class SmqlToAR
end
def quote_column_name name
- @quote.quote_column_name( name).gsub /"\."/, ','
+ @quoter.quote_column_name( name).gsub /"\."/, ','
end
def quote_table_name name
- name = case name
- when Array, Column::Col then @table_alias[Array.wrap name]
- else name.to_s
- end
- @quote.quote_table_name name
+ @quoter.quote_table_name( name).gsub /"\."/, ','
end
def column table, name
- "#{quote_table_name table}.#{quote_column_name name}"
+ "#{quote_table_name table.kind_of?(String) ? table : @table_alias[table]}.#{quote_column_name name}"
end
def build_join orig, pretable, table, prekey, key
- "\tLEFT JOIN #{orig} AS #{quote_table_name table}\n\tON #{column pretable, prekey} = #{column table, key}\n"
+ " JOIN #{quote_table_name orig.to_sym} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} "
end
- 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
- @table_model.rehash
- premodel = @table_model.find {|k,v| pretable == k }[1]
- t = @table_alias[ table]
- pt = quote_table_name table[ 0...-1]
- refl = premodel.reflections[table.last.to_sym]
- case refl
- when ActiveRecord::Reflection::ThroughReflection
- through = refl.through_reflection
- through_table = table[0...-1]+[Column::Col.new( through.name, table.last.as)]
- srctable = through_table+[Column::Col.new( refl.source_reflection.name, table.last.as)]
- @table_model[ srctable] = model
- @table_alias[ table] = @table_alias[ srctable]
- join_ through_table, through.klass, quote_table_name( through.table_name)
- join_ srctable, refl.klass, query, through_table
- when ActiveRecord::Reflection::AssociationReflection
- case refl.macro
- when :has_many, :has_one
- @_joins += build_join query, pretable, t, premodel.primary_key, refl.foreign_key
- when :belongs_to
- @_joins += build_join query, pretable, t, refl.foreign_key, premodel.primary_key
- when :has_and_belongs_to_many
- jointable = [Column::Col.new(',')] + table
- @_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
- else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}"
- end
- else raise BuilderError, "Unkown reflection type: #{refl.class.name} #{refl.macro.inspect}"
- end
- self
- end
-
- def joins table, model
- table = table.flatten.compact
+ def join table, model
return self if @_joined.include? table # Already joined
- join_ table, model, quote_table_name( model.table_name)
+ 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}"
+ end
@_joined.push table
+ self
end
def includes table
@@ -176,28 +111,36 @@ class SmqlToAR
end
def order table, col, o
- ct = column table, col
+ tc = column table, col
@_select.push ct
@_order.push "#{ct} #{:DESC == o ? :DESC : :ASC}"
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.type_correction!.optimize!.build_where
+ where_str = @_where.collect do |w|
+ w = Array.wrap w
+ 1 == w.length ? w.first : "( #{w.join( ' OR ')} )"
+ end.join ' AND '
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
@@ -216,75 +159,4 @@ class SmqlToAR
@model
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