Compare commits

...

35 commits

Author SHA1 Message Date
Denis Knauf a4ce8a1e6b v0.0.7 2012-07-03 16:28:46 +02:00
Denis Knauf 7f43e9fb86 not compatible to ruby1.8 now! new: ActiveRecordExtensions 2012-07-03 16:28:27 +02:00
Denis Knauf 3d922c5e47 v0.0.6 2012-02-09 14:10:58 +01:00
Denis Knauf 7229fd185c overlaps: key-pairs will be ored 2012-02-09 13:31:24 +01:00
Denis Knauf 08d1610987 optimizer optimized (AND[Or["abc"]] -> "abc"). ()-bugfix in where-clause. 2012-01-18 13:34:26 +01:00
Denis Knauf ce81d21bd8 v0.0.5.2. build-methods renamed to an underscored class-name with suffix _build and aliases build to these methods created. equal_join_build fixed; it never use col.joins.each. #foreigh_key 2012-01-17 10:52:31 +01:00
Denis Knauf fbf5a7019c v0.0.5.1 2012-01-16 12:52:28 +01:00
Denis Knauf 27639e24b7 #join: ",".to_alias OPS! 2012-01-16 12:52:01 +01:00
Denis Knauf 40deac93bf v0.0.5 2012-01-12 17:12:22 +01:00
Denis Knauf 6f8569f80a OVERLAPS-Operator <=> bzw. <!> implementiert. SmqlToAR.reload_library löscht zuerst SmqlToAR, läd dann erst neu. 2012-01-12 17:11:55 +01:00
Denis Knauf a4a1d1b9e9 v0.0.4.8 2012-01-12 16:55:25 +01:00
Denis Knauf f6b9579073 over-datanamelen-bug fixed 2012-01-12 16:54:51 +01:00
Denis Knauf 3ba285548c patch fix 2011-12-05 12:39:41 +01:00
Denis Knauf 3d08496ec4 ( ( ) ( ) ) 2011-11-30 16:20:56 +01:00
Denis Knauf db1eec6fa7 v0.0.4.7 2011-11-30 13:55:34 +01:00
Denis Knauf edcc68a4ed dependency to methodphitamine removed. 2011-11-30 13:52:19 +01:00
Denis Knauf 2b3922e158 v0.0.4.6 2011-11-29 12:02:54 +01:00
Denis Knauf 493556fda2 Column::Col is comparable 2011-11-29 12:01:38 +01:00
Denis Knauf 1c97319d9c Column::Col-fixes (==, ===) 2011-11-29 12:00:40 +01:00
Denis Knauf 293909b58b v0.0.4.5 2011-11-28 15:48:13 +01:00
Denis Knauf 080237c5d7 bugfixes: ranges, vid===Symbol, And/Or in NotInRange 2011-11-28 15:47:48 +01:00
Denis Knauf 1d6baf41e3 0.0.4.4 unstable? 2011-11-22 12:15:36 +01:00
Denis Knauf 63be31734b debugging removed 2011-11-22 12:14:22 +01:00
Denis Knauf 69aceef180 aliased tables. firm: { "people[users]": {type: "User"}, "people[old]": {"age>:": 50 } # Every ToDo which has a User as Person or people older than 50. It is not an OR, it is an AND, but {people:{type:"User","age>":50}} or {"people":{type:"User"},"people":{"age>:":50} means every User older than 50. "users" and "old" are independet questions and both must be present, if firm should be true. 2011-11-21 10:06:46 +01:00
Denis Knauf 8eebb3ac1f VERSION 0.0.4.3 2011-10-27 15:28:34 +02:00
Denis Knauf 3b60648ee7 Version 0.0.4.3 2011-10-27 15:22:49 +02:00
Denis Knauf 5c68b74ee4 lesser logging. order-bug fixed 2011-10-27 15:21:56 +02:00
Denis Knauf f9ee0754f1 Version 0.0.4.2 2011-10-27 14:26:53 +02:00
Denis Knauf f3bcdd4b25 has_one-reflection (like has_many), group & having. subqueries deactivated 2011-10-25 16:31:18 +02:00
Denis Knauf 8eeb66d99f order by subnodes 2011-10-25 14:30:09 +02:00
Denis Knauf d5aa6052ec lesser debug 2011-10-06 15:28:40 +02:00
Denis Knauf 11466211ac group and having clause init 2011-10-06 13:27:41 +02:00
Denis Knauf cc49fdefe9 v0.0.4.1 2011-10-06 13:24:44 +02:00
Denis Knauf 863ebca9cd fixes: OR... 2011-10-06 13:24:17 +02:00
Denis Knauf 09dfa782a8 debugging-output removed 2011-10-05 14:53:34 +02:00
6 changed files with 279 additions and 115 deletions

View file

@ -15,7 +15,6 @@ begin
gem.add_dependency 'activerecord'
gem.add_dependency 'activesupport'
gem.add_dependency 'json'
gem.add_dependency 'methodphitamine'
end
Jeweler::GemcutterTasks.new
rescue LoadError

2
TODO
View file

@ -1 +1,3 @@
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).

View file

@ -1 +1 @@
0.0.4
0.0.7

View file

@ -43,9 +43,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 +55,49 @@ 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
def initialize path
super :path => path
super path: path
end
end
class ConColumnError < SMQLError
def initialize expected, got
super :expected => expected, :got => got
super expected: expected, got: got
end
end
@ -107,9 +107,8 @@ class SmqlToAR
# Model der Relation `rel` von `model`
def self.model_of model, rel
rel = rel.to_sym
r = model.reflections[ rel].andand.klass
r.nil? && :self == rel ? model : r
r = model.reflections[ rel.to_sym].andand.klass
r.nil? && rel === :self ? model : r
end
# Eine Spalte in einer Tabelle, relativ zu `Column#model`.
@ -120,10 +119,43 @@ 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( &it.to_s.split( /[.\/]/)).flatten.collect( &:to_sym).reject( &it==:self)
*@path, @col = *Array.wrap( col).
collect {|x| x.to_s.split /[.\/]/ }.
flatten.
collect( &Col.method( :new)).
reject {|x| x === :self }
end
def last_model
@ -133,8 +165,7 @@ class SmqlToAR
def each
model = @model
@path.each do |rel|
rel = rel.to_sym
unless :self == rel
unless rel === :self
model = SmqlToAR.model_of model, rel
return false unless model
yield rel, model
@ -169,6 +200,7 @@ class SmqlToAR
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
@ -190,10 +222,8 @@ 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 <<self
def logger=(logger) @@logger = logger end
@ -213,8 +243,8 @@ class SmqlToAR
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}
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
@ -224,35 +254,35 @@ class SmqlToAR
end
def parse
benchmark 'SMQL parse' do
#benchmark 'SMQL parse' do
@conditions = ConditionTypes.try_parse @model, @query
end
#end
#p conditions: @conditions
self
end
def build prefix = nil
benchmark 'SMQL build query' do
@builder = QueryBuilder.new @model, prefix
def build
#benchmark 'SMQL build query' do
@builder = QueryBuilder.new @model
table = @builder.base_table
@conditions.each &it.build( builder, table)
end
@conditions.each {|condition| condition.build builder, table }
#end
#p builder: @builder
self
end
def ar
@ar ||= benchmark 'SMQL ar' do
@ar ||= #benchmark 'SMQL ar' do
@builder.to_ar
end
#end
end
def to_ar
benchmark 'SMQL' do
#benchmark 'SMQL' do
parse
build
ar
end
#end
end
def self.to_ar *params
@ -262,8 +292,27 @@ class SmqlToAR
def self.reload_library
lib_dir = File.dirname __FILE__
fj = lambda {|*a| File.join lib_dir, *a }
load fj.call( 'smql_to_ar.rb')
load fj.call( 'smql_to_ar', 'condition_types.rb')
load fj.call( 'smql_to_ar', 'query_builder.rb')
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
ActiveRecord::Base.send :include, SmqlToAR::ActiveRecordExtensions

View file

@ -14,6 +14,9 @@
# You should have received a copy of the GNU General Public License
# 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
#############################################################################
# Alle Subklassen (qualitativ: ConditionTypes::*), die als Superklasse Condition haben,
@ -56,7 +59,7 @@ class SmqlToAR
r = nil
#p :try_parse => { :model => model, :colop => colop, :value => val }
conditions.each do |c|
raise_unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/, UnexpectedColOpError.new( model, colop, val)
raise_unless colop =~ /^(?:\d*:)?(.*?)((?:\W*(?!\])\W)?)$/, UnexpectedColOpError.new( model, colop, val)
col, op = $1, $2
col = split_keys( col).collect {|c| Column.new model, c }
r = c.try_parse model, col, op, val
@ -99,16 +102,17 @@ class SmqlToAR
# Versuche das Objekt zu erkennen. Operator und Expected muessen passen.
# Passt das Object, die Klasse instanzieren.
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
new model, cols, val if self::Operator === op and self::Expected.any?( &it === val)
#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? {|x| x === val}
end
def inspect
"#{self.name}(:operator=>#{self::Operator.inspect}, :expected=>#{self::Expected.inspect}, :where=>#{self::Where.inspect})"
"#{self.name}( :operator=>#{self::Operator.inspect}, :expected=>#{self::Expected.inspect}, :where=>#{self::Where.inspect})"
end
end
def initialize model, cols, val
#p init: self, caller: caller
@model, @cols = model, cols
@value = case val
when Hash, Range then val
@ -149,9 +153,9 @@ class SmqlToAR
# 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 build builder, table
def condition_build builder, table
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
@cols.each do |col|
col.joins builder, table
@ -159,25 +163,27 @@ class SmqlToAR
builder.where values.keys.collect {|vid| self.class::Where % [ col, vid.to_s ] }
end
else
b2 = SmqlToAR::And.new builder
values.keys.each do |vid|
builder.where @cols.collect {|col|
b2.where SmqlToAR::Or[ *@cols.collect {|col|
col.joins builder, table
col = builder.column table+col.path, col.col
self.class::Where % [ col, vid.to_s ]
}
}]
end
end
self
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 initialze model, cols, val
if Array === val && 2 == val.length
def initialize model, cols, val
if Array === val
f, l = val
f, l = Time.parse(f), Time.parse(l) if f.kind_of? String
val = f..l
@ -185,23 +191,65 @@ class SmqlToAR
super model, cols, val
end
def build builder, table
builder.wobs (v1 = builder.vid) => @value.begin, (v2 = builder.vid) => @value.end
def not_in_range_build builder, table
builder.wobs (v1 = builder.vid).to_sym => @value.begin, (v2 = builder.vid).to_sym => @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"
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
Operator = '!|='
Where = "%s NOT IN (%s)"
Expected = [Array]
def build builder, table
def not_in_build builder, table
builder.wobs (v = builder.vid).to_sym => @value
@cols.each do |col|
col.joins builder, table
@ -209,6 +257,7 @@ class SmqlToAR
end
self
end
alias build not_in_build
end
In = simple_condition NotIn, '|=', '%s IN (%s)', [Array]
@ -217,19 +266,25 @@ class SmqlToAR
NotEqual2 = simple_condition Condition, '<>', "%s <> %s", [Array, String, Numeric]
GreaterThanOrEqual = simple_condition Condition, '>=', "%s >= %s", [Array, Numeric]
LesserThanOrEqual = simple_condition Condition, '<=', "%s <= %s", [Array, Numeric]
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
Operator = '=>'
Expected = [Hash, lambda {|x| x.kind_of?( Array) and x.all?( &it.kind_of?( Hash))}]
Expected = [Hash, lambda {|x| x.kind_of?( Array) and x.all? {|y| y.kind_of?( Hash) }}]
def initialize *pars
super( *pars)
@value = Array.wrap @value
cols = {}
p self: self, cols: @cols
@cols.each do |col|
col_model = col.relation
cols[col] = [col_model] + @value.collect {|val| ConditionTypes.try_parse( col_model, val) }
@ -241,18 +296,27 @@ class SmqlToAR
raise_unless col.relation, NonExistingRelationError.new( %w[Relation], col)
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|
model, *sub = sub
t = table + col.path + [col.col]
col.joins.each {|j, m| builder.joins table+j, m }
col.joins builder, table
builder.joins t, model
b2 = 1 == sub.length ? builder : Or.new( builder)
sub.each {|i| i.collect( &it.build( And.new( b2), t)); p 'or' => b2 }
b4 = b3.new( b2)
sub.each do |i|
b5 = And.new b4
i.collect {|j| j.build b5, t }
end
end
ap '=>' => builder
self
end
alias build equal_join_build
end
# Takes to Queries.
@ -267,6 +331,7 @@ class SmqlToAR
# 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))}]
@ -274,31 +339,36 @@ class SmqlToAR
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 }
@cols.each {|col, sub| sub[ 1..-1] = SmqlToAR.new( col.relation, val[0]).parse, *sub[-1] }
end
def verify_column col
raise_unless col.child?, ConColumnError.new( [:Column], col)
end
def build builder, table
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]
sub[2..-1].each &it.build( builder, t)
#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]
Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric]
Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric, Date, Time]
Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric, Date, Time]
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', [true]
NotExists = simple_condition Condition, '', '%s IS NULL', [false]
Exists = simple_condition Condition, '', '%s IS NOT NULL', [TrueClass]
NotExists = simple_condition Condition, '', '%s IS NULL', [FalseClass]
Join = simple_condition EqualJoin, '', nil, [Hash]
InRange2 = simple_condition InRange, '', nil, [Range]
@ -310,7 +380,7 @@ class SmqlToAR
raise_unless col.exist_in? || SmqlToAR.model_of( col.last_model, col.col), NonExistingSelectableError.new( col)
end
def build builder, table
def select_build builder, table
@cols.each do |col|
if col.exist_in?
col.joins builder, table
@ -322,6 +392,7 @@ class SmqlToAR
end
self
end
alias build select_build
end
class Functions < Condition
@ -336,12 +407,11 @@ class SmqlToAR
class <<self
def 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?( &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})"
"#{self.name}( :name=>#{self::Name}, :expected=>#{self::Expected})"
end
end
@ -371,40 +441,42 @@ class SmqlToAR
super model, func, args
end
def build builder, table
def order_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_unless 1 == t.length, RootOnlyFunctionError.new( t)
builder.order t, col.col, o
end
end
alias build order_build
end
class Limit < Function
Name = :limit
Expected = [Fixnum]
def build builder, table
def limit_build builder, table
raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
builder.limit @args
builder.limit = Array.wrap(@args).first.to_i
end
alias build limit_build
end
class Offset < Function
Name = :offset
Expected = [Fixnum]
def build builder, table
def offset_build builder, table
raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
builder.offset @args
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

View file

@ -26,6 +26,42 @@ class SmqlToAR
def to_sym() "smql_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
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
@ -34,14 +70,11 @@ class SmqlToAR
def initialize model, prefix = nil
@prefix = "smql"
@logger = SmqlToAR.logger
@table_alias = Hash.new do |h, k|
j = Array.wrap( k).compact
h[k] = h.key?(j) ? h[j] : "#{@prefix},#{j.join(',')}"
end
@_vid, @_where, @_wobs, @model, @quoter = 0, SmqlToAR::And[], {}, model, model.connection
@base_table = [model.table_name.to_sym]
@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[ @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}.*"], "", [@base_table], [], []
@table_model = {@base_table => @model}
end
@ -49,7 +82,7 @@ class SmqlToAR
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}>"
"#<#{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.
@ -67,19 +100,23 @@ class SmqlToAR
end
def quote_column_name name
@quoter.quote_column_name( name).gsub /"\."/, ','
@quote.quote_column_name( name).gsub /"\."/, ','
end
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
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
def build_join orig, pretable, table, prekey, key
" LEFT OUTER 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
def sub_joins table, col, model, query
@ -90,32 +127,33 @@ class SmqlToAR
def join_ table, model, query, pretable = nil
pretable ||= table[0...-1]
@table_model[ table] = model
premodel = @table_model[ pretable]
@table_model.rehash
premodel = @table_model.find {|k,v| pretable == k }[1]
t = @table_alias[ table]
pt = quote_table_name @table_alias[ table[ 0...-1]]
refl = premodel.reflections[table.last]
pt = quote_table_name table[ 0...-1]
refl = premodel.reflections[table.last.to_sym]
case refl
when ActiveRecord::Reflection::ThroughReflection
through = refl.through_reflection
throughtable = table[0...-1]+[through.name.to_sym]
srctable = throughtable+[refl.source_reflection.name]
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_ throughtable, through.klass, quote_table_name( through.table_name)
join_ srctable, refl.klass, query, throughtable
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
@_joins += build_join query, pretable, t, premodel.primary_key, refl.primary_key_name
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.primary_key_name, premodel.primary_key
@_joins += build_join query, pretable, t, refl.foreign_key, 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
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}"
else raise BuilderError, "Unkown reflection type: #{refl.class.name} #{refl.macro.inspect}"
end
self
end
@ -138,17 +176,19 @@ class SmqlToAR
end
def order table, col, o
@_order.push "#{column table, col} #{:DESC == o ? :DESC : :ASC}"
ct = column table, col
@_select.push ct
@_order.push "#{ct} #{:DESC == o ? :DESC : :ASC}"
self
end
def build_ar
where_str = @_where.type_correction!.optimize!.tap {|x| p x }.build_where
where_str = @_where.type_correction!.optimize!.build_where
incls = {}
@_includes.each do |inc|
b = incls
inc[1..-1].collect {|rel| b = b[rel] ||= {} }
end
@logger.info where: where_str, wobs: @_wobs
@model = @model.
select( @_select.join( ', ')).
joins( @_joins).
@ -179,10 +219,9 @@ class SmqlToAR
class SubBuilder < Array
attr_reader :parent, :_where
delegate :wobs, :joins, :includes, :sub_joins, :vid, :quote_column_name, :quoter, :quote_table_name, :column, :to => :parent
delegate :wobs, :joins, :includes, :sub_joins, :vid, :quote_column_name, :quote, :quote_table_name, :column, :to => :parent
def initialize parent, tmp = false
p init: self, parent: parent
@parent = parent
@parent.where self unless @parend.nil? && tmp
end
@ -207,21 +246,20 @@ class SmqlToAR
end
def optimize!
p optimize: self
ext = []
collect! do |sub|
sub = sub.optimize! if sub.kind_of? Array
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!
p optimized: self
p ext: ext
push *ext
self
end
@ -231,18 +269,22 @@ class SmqlToAR
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
join ' AND '
def build_where indent = nil
collect_build_where( indent).join " AND\n"+"\t"*(indent||0)
end
end
class Or < SubBuilder
def build_where
join ' OR '
def build_where indent = nil
collect_build_where( indent).join " OR\n"+"\t"*(indent||0)
end
end
end