smql 0.0.4.2 → 0.0.4.3

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.0.4.3
@@ -25,28 +25,44 @@ class SmqlToAR
25
25
  # Nimmt eine Klasse ein Objekt an, so soll diese Klasse instanziert werden.
26
26
  # Alles weitere siehe Condition.
27
27
  module ConditionTypes
28
+ extend SmqlToAR::Assertion
29
+
28
30
  class <<self
29
31
  # Ex: 'givenname|surname|nick' => [:givenname, :surname, :nick]
30
32
  def split_keys k
31
33
  k.split( '|').collect &:to_sym
32
34
  end
33
35
 
36
+ def conditions &e
37
+ unless block_given?
38
+ r = Enumerator.new( self, :conditions)
39
+ s = self
40
+ r.define_singleton_method :[] do |k|
41
+ s.conditions.select {|c| c::Operator === k }
42
+ end
43
+ return r
44
+ end
45
+ constants.each do |c|
46
+ next if :Condition == c
47
+ c = const_get c
48
+ next if Condition === c
49
+ yield c
50
+ end
51
+ end
52
+
34
53
  # Eine Regel parsen.
35
54
  # Ex: Person, "givenname=", "Peter"
36
55
  def try_parse_it model, colop, val
37
56
  r = nil
38
57
  #p :try_parse => { :model => model, :colop => colop, :value => val }
39
- constants.each do |c|
40
- next if :Condition == c
41
- c = const_get c
42
- next if Condition === c
43
- raise UnexpectedColOpError.new( model, colop, val) unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/
58
+ conditions.each do |c|
59
+ raise_unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/, UnexpectedColOpError.new( model, colop, val)
44
60
  col, op = $1, $2
45
61
  col = split_keys( col).collect {|c| Column.new model, c }
46
62
  r = c.try_parse model, col, op, val
47
63
  break if r
48
64
  end
49
- raise UnexpectedError.new( model, colop, val) unless r
65
+ raise_unless r, UnexpectedError.new( model, colop, val)
50
66
  r
51
67
  end
52
68
 
@@ -72,16 +88,24 @@ class SmqlToAR
72
88
  end
73
89
 
74
90
  class Condition
91
+ include SmqlToAR::Assertion
92
+ extend SmqlToAR::Assertion
75
93
  attr_reader :value, :cols
76
94
  Operator = nil
77
95
  Expected = []
78
96
  Where = nil
79
97
 
80
- # Versuche das Objekt zu erkennen. Operator und Expected muessen passen.
81
- # Passt das Object, die Klasse instanzieren.
82
- def self.try_parse model, cols, op, val
83
- #p :self => name, :try_parse => op, :cols => cols, :with => self::Operator, :value => val, :expected => self::Expected, :model => model.name
84
- new model, cols, val if self::Operator === op and self::Expected.any? {|i| i === val }
98
+ class <<self
99
+ # Versuche das Objekt zu erkennen. Operator und Expected muessen passen.
100
+ # Passt das Object, die Klasse instanzieren.
101
+ def try_parse model, cols, op, val
102
+ #p :self => name, :try_parse => op, :cols => cols, :with => self::Operator, :value => val, :expected => self::Expected, :model => model.name
103
+ new model, cols, val if self::Operator === op and self::Expected.any?( &it === val)
104
+ end
105
+
106
+ def inspect
107
+ "#{self.name}(:operator=>#{self::Operator.inspect}, :expected=>#{self::Expected.inspect}, :where=>#{self::Where.inspect})"
108
+ end
85
109
  end
86
110
 
87
111
  def initialize model, cols, val
@@ -93,6 +117,10 @@ class SmqlToAR
93
117
  verify
94
118
  end
95
119
 
120
+ def inspect
121
+ "#<#{self.class.name}:0x#{(self.object_id<<1).to_s 16} model: #{self.class.name}, cols: #{@cols.inspect}, value: #{@value.inspect}>"
122
+ end
123
+
96
124
  def verify
97
125
  @cols.each do |col|
98
126
  verify_column col
@@ -103,19 +131,19 @@ class SmqlToAR
103
131
  # Gibt es eine Spalte diesen Namens?
104
132
  # Oder: Gibt es eine Relation diesen Namens? (Hier nicht der Fall)
105
133
  def verify_column col
106
- raise NonExistingColumnError.new( %w[Column], col) unless col.exist_in?
134
+ raise_unless col.exist_in?, NonExistingColumnError.new( %w[Column], col)
107
135
  end
108
136
 
109
137
  # Modelle koennen Spalten/Relationen verbieten mit Model#smql_protected.
110
138
  # Dieses muss ein Object mit #include?( name_als_string) zurueckliefern,
111
139
  # welches true fuer verboten und false fuer, erlaubt steht.
112
140
  def verify_allowed col
113
- raise ProtectedColumnError.new( col) if col.protected?
141
+ raise_if col.protected?, ProtectedColumnError.new( col)
114
142
  end
115
143
 
116
144
  # Erstelle alle noetigen Klauseln. builder nimmt diese entgegen,
117
- # wobei builder.join, builder.select, builder.where und builder.wobs von interesse sind.
118
- # mehrere Schluessel bedeuten, dass die Values _alle_ zutreffen muessen, wobei die Schluessel geodert werden.
145
+ # wobei builder.joins, builder.select, builder.where und builder.wobs von interesse sind.
146
+ # mehrere Schluessel bedeuten, dass die Values _alle_ zutreffen muessen, wobei die Schluessel geODERt werden.
119
147
  # Ex:
120
148
  # 1) {"givenname=", "Peter"} #=> givenname = 'Peter'
121
149
  # 2) {"givenname=", ["Peter", "Hans"]} #=> ( givenname = 'Peter' OR givenname = 'Hans' )
@@ -128,15 +156,16 @@ class SmqlToAR
128
156
  @cols.each do |col|
129
157
  col.joins builder, table
130
158
  col = builder.column table+col.path, col.col
131
- builder.where *values.keys.collect {|vid| self.class::Where % [ col, vid.to_s ] }
159
+ builder.where values.keys.collect {|vid| self.class::Where % [ col, vid.to_s ] }
132
160
  end
133
161
  else
162
+ b2 = SmqlToAR::And.new builder
134
163
  values.keys.each do |vid|
135
- builder.where *@cols.collect {|col|
164
+ b2.where SmqlToAR::Or[ *@cols.collect {|col|
136
165
  col.joins builder, table
137
166
  col = builder.column table+col.path, col.col
138
167
  self.class::Where % [ col, vid.to_s ]
139
- }
168
+ }]
140
169
  end
141
170
  end
142
171
  self
@@ -185,50 +214,94 @@ class SmqlToAR
185
214
 
186
215
  In = simple_condition NotIn, '|=', '%s IN (%s)', [Array]
187
216
  In2 = simple_condition In, '', nil, [Array]
188
- NotEqual = simple_condition Condition, /\!=|<>/, "%s <> %s", [Array, String, Numeric]
217
+ NotEqual = simple_condition Condition, '!=', "%s <> %s", [Array, String, Numeric]
218
+ NotEqual2 = simple_condition Condition, '<>', "%s <> %s", [Array, String, Numeric]
189
219
  GreaterThanOrEqual = simple_condition Condition, '>=', "%s >= %s", [Array, Numeric]
190
220
  LesserThanOrEqual = simple_condition Condition, '<=', "%s <= %s", [Array, Numeric]
221
+
222
+ # Examples:
223
+ # { 'articles=>' => { id: 1 } }
224
+ # { 'articles=>' => [ { id: 1 }, { id: 2 } ] }
191
225
  class EqualJoin <Condition
192
- Operator = '='
193
- Expected = [Hash]
226
+ Operator = '=>'
227
+ Expected = [Hash, lambda {|x| x.kind_of?( Array) and x.all?( &it.kind_of?( Hash))}]
194
228
 
195
229
  def initialize *pars
196
230
  super( *pars)
231
+ @value = Array.wrap @value
197
232
  cols = {}
198
233
  @cols.each do |col|
199
- col_model = SmqlToAR.model_of col.last_model, col.col
200
- #p col_model: col_model.to_s, value: @value
201
- cols[col] = [col_model] + ConditionTypes.try_parse( col_model, @value)
234
+ col_model = col.relation
235
+ cols[col] = [col_model] + @value.collect {|val| ConditionTypes.try_parse( col_model, val) }
202
236
  end
203
237
  @cols = cols
204
238
  end
205
239
 
206
240
  def verify_column col
207
- refl = SmqlToAR.model_of col.last_model, col.col
208
- #p refl: refl, model: @model.name, col: col, :reflections => @model.reflections.keys
209
- raise NonExistingRelationError.new( %w[Relation], col) unless refl
241
+ raise_unless col.relation, NonExistingRelationError.new( %w[Relation], col)
210
242
  end
211
243
 
212
244
  def build builder, table
213
245
  @cols.each do |col, sub|
246
+ model, *sub = sub
214
247
  t = table + col.path + [col.col]
215
- #p sub: sub
216
- p col: col, joins: col.joins
217
- col.joins.each {|j, m| builder.join table+j, m }
218
- builder.join t, SmqlToAR.model_of( col.last_model, col.col)
219
- sub[1..-1].each {|one| one.build builder, t }
248
+ col.joins.each {|j, m| builder.joins table+j, m }
249
+ builder.joins t, model
250
+ b2 = 1 == sub.length ? builder : Or.new( builder)
251
+ sub.each {|i| i.collect( &it.build( And.new( b2), t)); p 'or' => b2 }
252
+ end
253
+ self
254
+ end
255
+ end
256
+
257
+ # Takes to Queries.
258
+ # First Query will be a Subquery, second a regular query.
259
+ # Example:
260
+ # Person.smql 'sub.articles:' => [{'limit:' => 1, 'order:': 'updated_at desc'}, {'content~' => 'some text'}]
261
+ # Person must have as last Article (compared by updated_at) owned by Person a Artive which has 'some text' in content.
262
+ # The last Article needn't to have 'some text' has content, the subquery takes it anyway.
263
+ # But the second query compares to it and never to any other Article, because these are filtered by first query.
264
+ # The difference to
265
+ # Person.smql :articles => {'content~' => 'some text', 'limit:' => 1, 'order:': 'updated_at desc'}
266
+ # is, second is not allowed (limit and order must be in root) and this means something like
267
+ # "Person must have the Article owned by Person which has 'some text' in content.
268
+ # limit and order has no function in this query and this article needn't to be the last."
269
+ =begin
270
+ class SubEqualJoin < EqualJoin
271
+ Operator = '()'
272
+ Expected = [lambda {|x| x.kind_of?( Array) and (1..2).include?( x.length) and x.all?( &it.kind_of?( Hash))}]
273
+
274
+ def initialize model, cols, val
275
+ super model, cols, val[1]
276
+ # sub: model, subquery, sub(condition)
277
+ @cols.each {|col, sub| sub[ 1..-1] = SmqlToAR.new( col.relation, val[0]).parse, *sub[-1] }
278
+ end
279
+
280
+ def verify_column col
281
+ raise_unless col.child?, ConColumnError.new( [:Column], col)
282
+ end
283
+
284
+ def build builder, table
285
+ @cols.each do |col, sub|
286
+ t = table+col.to_a
287
+ builder.sub_joins t, col, *sub[0..1]
288
+ #ap sub: sub[2..-1]
289
+ sub[2..-1].each &it.build( builder, t)
220
290
  end
221
291
  self
222
292
  end
223
293
  end
294
+ =end
295
+
224
296
  Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric]
225
297
  Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric]
226
298
  GreaterThan = simple_condition Condition, '>', "%s > %s", [Array, Numeric]
227
299
  LesserThan = simple_condition Condition, '<', "%s < %s", [Array, Numeric]
228
300
  NotIlike = simple_condition Condition, '!~', "%s NOT ILIKE %s", [Array, String]
229
301
  Ilike = simple_condition Condition, '~', "%s ILIKE %s", [Array, String]
302
+ Exists = simple_condition Condition, '', '%s IS NOT NULL', [TrueClass]
303
+ NotExists = simple_condition Condition, '', '%s IS NULL', [FalseClass]
230
304
 
231
- ####### No Operator #######
232
305
  Join = simple_condition EqualJoin, '', nil, [Hash]
233
306
  InRange2 = simple_condition InRange, '', nil, [Range]
234
307
  class Select < Condition
@@ -236,7 +309,7 @@ class SmqlToAR
236
309
  Expected = [nil]
237
310
 
238
311
  def verify_column col
239
- raise NonExistingSelectableError.new( col) unless col.exist_in? or SmqlToAR.model_of( col.last_model, col.col)
312
+ raise_unless col.exist_in? || SmqlToAR.model_of( col.last_model, col.col), NonExistingSelectableError.new( col)
240
313
  end
241
314
 
242
315
  def build builder, table
@@ -258,13 +331,19 @@ class SmqlToAR
258
331
  Expected = [String, Array, Hash, Numeric, nil]
259
332
 
260
333
  class Function
334
+ include SmqlToAR::Assertion
261
335
  Name = nil
262
336
  Expected = []
263
337
  attr_reader :model, :func, :args
264
338
 
265
- def self.try_parse model, func, args
266
- SmqlToAR.logger.info( { try_parse: [func,args]}.inspect)
267
- self.new model, func, args if self::Name === func and self::Expected.any? {|e| e === args }
339
+ class <<self
340
+ def try_parse model, func, args
341
+ self.new model, func, args if self::Name === func and self::Expected.any?( &it === args)
342
+ end
343
+
344
+ def inspect
345
+ "#{self.name}(:name=>#{self::Name}, :expected=>#{self::Expected})"
346
+ end
268
347
  end
269
348
 
270
349
  def initialize model, func, args
@@ -277,22 +356,19 @@ class SmqlToAR
277
356
  Expected = [String, Array, Hash, nil]
278
357
 
279
358
  def initialize model, func, args
280
- SmqlToAR.logger.info( {args: args}.inspect)
281
359
  args = case args
282
360
  when String then [args]
283
361
  when Array, Hash then args.to_a
284
362
  when nil then nil
285
363
  else raise 'Oops'
286
364
  end
287
- SmqlToAR.logger.info( {args: args}.inspect)
288
365
  args.andand.collect! do |o|
289
366
  o = Array.wrap o
290
367
  col = Column.new model, o.first
291
368
  o = 'desc' == o.last.to_s.downcase ? :DESC : :ASC
292
- raise NonExistingColumnError.new( [:Column], col) unless col.exist_in?
369
+ raise_unless col.exist_in?, NonExistingColumnError.new( [:Column], col)
293
370
  [col, o]
294
371
  end
295
- SmqlToAR.logger.info( {args: args}.inspect)
296
372
  super model, func, args
297
373
  end
298
374
 
@@ -302,22 +378,39 @@ class SmqlToAR
302
378
  col, o = o
303
379
  col.joins builder, table
304
380
  t = table + col.path
305
- #raise OnlyOrderOnBaseError.new( t) unless 1 == t.length
381
+ #raise_unless 1 == t.length, RootOnlyFunctionError.new( t)
306
382
  builder.order t, col.col, o
307
383
  end
308
384
  end
309
385
  end
310
386
 
387
+ class Limit < Function
388
+ Name = :limit
389
+ Expected = [Fixnum]
390
+
391
+ def build builder, table
392
+ raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
393
+ builder.limit = Array.wrap(@args).first.to_i
394
+ end
395
+ end
396
+
397
+ class Offset < Function
398
+ Name = :offset
399
+ Expected = [Fixnum]
400
+
401
+ def build builder, table
402
+ raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
403
+ builder.offset = Array.wrap(@args).first.to_i
404
+ end
405
+ end
406
+
311
407
  def self.new model, col, val
312
- SmqlToAR.logger.info( { function: col.first.to_sym }.inspect)
313
408
  r = nil
314
409
  constants.each do |c|
315
410
  next if [:Function, :Where, :Expected, :Operator].include? c
316
411
  c = const_get c
317
412
  next if Function === c or not c.respond_to?( :try_parse)
318
- SmqlToAR.logger.info( {f: c}.inspect)
319
413
  r = c.try_parse model, col.first.to_sym, val
320
- SmqlToAR.logger.info( {r: r}.inspect)
321
414
  break if r
322
415
  end
323
416
  r
@@ -22,36 +22,41 @@ class SmqlToAR
22
22
  class Vid
23
23
  attr_reader :vid
24
24
  def initialize( vid) @vid = vid end
25
- def to_s() ":c#{@vid}" end
26
- def to_sym() "c#{@vid}".to_sym end
25
+ def to_s() ":smql_c#{@vid}" end
26
+ def to_sym() "smql_c#{@vid}".to_sym end
27
27
  alias sym to_sym
28
28
  def to_i() @vid end
29
29
  end
30
30
 
31
- attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins
32
- attr_accessor :logger
31
+ attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins, :prefix, :_vid
32
+ attr_accessor :logger, :limit, :offset
33
33
 
34
- def initialize model
34
+ def initialize model, prefix = nil
35
+ @prefix = "smql"
35
36
  @logger = SmqlToAR.logger
36
37
  @table_alias = Hash.new do |h, k|
37
- k = Array.wrap k
38
- h[k] = "smql,#{k.join(',')}"
38
+ j = Array.wrap( k).compact
39
+ h[k] = h.key?(j) ? h[j] : "#{@prefix},#{j.join(',')}"
39
40
  end
40
- @_vid, @_where, @_wobs, @model, @quoter = 0, [], {}, model, model.connection
41
+ @_vid, @_where, @_wobs, @model, @quoter = 0, SmqlToAR::And[], {}, model, model.connection
41
42
  @base_table = [model.table_name.to_sym]
42
43
  @table_alias[ @base_table] = @base_table.first
43
44
  t = quote_table_name @table_alias[ @base_table]
44
- @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [], [], []
45
+ @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [@base_table], [], []
45
46
  @table_model = {@base_table => @model}
46
47
  end
47
48
 
48
49
  def vid() Vid.new( @_vid+=1) end
49
50
 
51
+ def inspect
52
+ "#<#{self.class.name}:#{"0x%x"% (self.object_id<<1)}|#{@prefix}:#{@base_table}:#{@model} vid=#{@_vid} where=#{@_where} wobs=#{@_wobs} select=#{@_select} aliases=#{@_table_alias}>"
53
+ end
54
+
50
55
  # Jede via where uebergebene Condition wird geodert und alle zusammen werden geundet.
51
56
  # "Konjunktive Normalform". Allerdings duerfen Conditions auch Komplexe Abfragen enthalten.
52
- # Ex: builder.where( 'a = a', 'b = c').where( 'c = d', 'e = e').where( 'x = y').where( '( m = n AND o = p )', 'f = g')
57
+ # Ex: builder.where( ['a = a', 'b = c']).where( ['c = d', 'e = e']).where( 'x = y').where( ['( m = n AND o = p )', 'f = g'])
53
58
  # #=> WHERE ( a = a OR b = c ) AND ( c = d OR e = e ) AND x = y ( ( m = n AND o = p ) OR f = g )
54
- def where *cond
59
+ def where cond
55
60
  @_where.push cond
56
61
  self
57
62
  end
@@ -74,32 +79,54 @@ class SmqlToAR
74
79
  end
75
80
 
76
81
  def build_join orig, pretable, table, prekey, key
77
- " JOIN #{quote_table_name orig.to_sym} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} "
82
+ " LEFT JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} "
78
83
  end
79
84
 
80
- def join table, model
81
- return self if @_joined.include? table # Already joined
82
- pretable = table[0...-1]
85
+ def sub_joins table, col, model, query
86
+ prefix, base_table = "#{@prefix}_sub", col.relation.table_name
87
+ join_ table, model, "(#{query.build( prefix).ar.to_sql})"
88
+ end
89
+
90
+ def join_ table, model, query, pretable = nil
91
+ pretable ||= table[0...-1]
83
92
  @table_model[ table] = model
84
93
  premodel = @table_model[ pretable]
85
94
  t = @table_alias[ table]
86
95
  pt = quote_table_name @table_alias[ table[ 0...-1]]
87
96
  refl = premodel.reflections[table.last]
88
- case refl.macro
89
- when :has_many
90
- @_joins += build_join model.table_name, pretable, t, premodel.primary_key, refl.primary_key_name
91
- when :belongs_to
92
- @_joins += build_join model.table_name, pretable, t, refl.primary_key_name, premodel.primary_key
93
- when :has_and_belongs_to_many
94
- jointable = [','] + table
95
- @_joins += build_join refl.options[:join_table], pretable, @table_alias[jointable], premodel.primary_key, refl.primary_key_name
96
- @_joins += build_join model.table_name, jointable, t, refl.association_foreign_key, refl.association_primary_key
97
- else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}"
97
+ case refl
98
+ when ActiveRecord::Reflection::ThroughReflection
99
+ through = refl.through_reflection
100
+ throughtable = table[0...-1]+[through.name.to_sym]
101
+ srctable = throughtable+[refl.source_reflection.name]
102
+ @table_model[ srctable] = model
103
+ @table_alias[ table] = @table_alias[ srctable]
104
+ join_ throughtable, through.klass, quote_table_name( through.table_name)
105
+ join_ srctable, refl.klass, query, throughtable
106
+ when ActiveRecord::Reflection::AssociationReflection
107
+ case refl.macro
108
+ when :has_many, :has_one
109
+ @_joins += build_join query, pretable, t, premodel.primary_key, refl.primary_key_name
110
+ when :belongs_to
111
+ @_joins += build_join query, pretable, t, refl.primary_key_name, premodel.primary_key
112
+ when :has_and_belongs_to_many
113
+ jointable = [','] + table
114
+ @_joins += build_join refl.options[:join_table], pretable, @table_alias[jointable], premodel.primary_key, refl.primary_key_name
115
+ @_joins += build_join query, jointable, t, refl.association_foreign_key, refl.association_primary_key
116
+ else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}"
117
+ end
118
+ else raise BuilderError, "Unkown reflection type: #{refl.class.name}"
98
119
  end
99
- @_joined.push table
100
120
  self
101
121
  end
102
122
 
123
+ def joins table, model
124
+ table = table.flatten.compact
125
+ return self if @_joined.include? table # Already joined
126
+ join_ table, model, quote_table_name( model.table_name)
127
+ @_joined.push table
128
+ end
129
+
103
130
  def includes table
104
131
  @_includes.push table
105
132
  self
@@ -111,36 +138,28 @@ class SmqlToAR
111
138
  end
112
139
 
113
140
  def order table, col, o
114
- tc = column table, col
141
+ ct = column table, col
115
142
  @_select.push ct
116
143
  @_order.push "#{ct} #{:DESC == o ? :DESC : :ASC}"
117
144
  self
118
145
  end
119
146
 
120
- class Dummy
121
- def method_missing m, *a, &e
122
- #p :dummy => m, :pars => a, :block => e
123
- self
124
- end
125
- end
126
-
127
147
  def build_ar
128
- where_str = @_where.collect do |w|
129
- w = Array.wrap w
130
- 1 == w.length ? w.first : "( #{w.join( ' OR ')} )"
131
- end.join ' AND '
148
+ where_str = @_where.type_correction!.optimize!.build_where
132
149
  incls = {}
133
150
  @_includes.each do |inc|
134
151
  b = incls
135
152
  inc[1..-1].collect {|rel| b = b[rel] ||= {} }
136
153
  end
137
- @logger.debug incls: incls, joins: @_joins
138
154
  @model = @model.
139
155
  select( @_select.join( ', ')).
140
156
  joins( @_joins).
141
157
  where( where_str, @_wobs).
142
158
  order( @_order.join( ', ')).
143
159
  includes( incls)
160
+ @model = @model.limit @limit if @limit
161
+ @model = @model.offset @offset if @offset
162
+ @model
144
163
  end
145
164
 
146
165
  def fix_calculate
@@ -159,4 +178,72 @@ class SmqlToAR
159
178
  @model
160
179
  end
161
180
  end
181
+
182
+ class SubBuilder < Array
183
+ attr_reader :parent, :_where
184
+ delegate :wobs, :joins, :includes, :sub_joins, :vid, :quote_column_name, :quoter, :quote_table_name, :column, :to => :parent
185
+
186
+ def initialize parent, tmp = false
187
+ @parent = parent
188
+ @parent.where self unless @parend.nil? && tmp
189
+ end
190
+
191
+ def new parent, tmp = false
192
+ super parent, tmp
193
+ #return parent if self.class == parent.class
194
+ #super parent
195
+ end
196
+
197
+ alias where push
198
+
199
+ def type_correction!
200
+ collect! do |sub|
201
+ if sub.kind_of? Array
202
+ sub = default[ *sub] unless sub.respond_to?( :type_correction!)
203
+ sub.type_correction!
204
+ end
205
+ sub
206
+ end
207
+ self
208
+ end
209
+
210
+ def optimize!
211
+ ext = []
212
+ collect! do |sub|
213
+ sub = sub.optimize! if sub.kind_of? Array
214
+ if self.class == sub.class
215
+ ext.push *sub
216
+ nil
217
+ elsif sub.blank?
218
+ nil
219
+ else
220
+ sub
221
+ end
222
+ end.compact!
223
+ push *ext
224
+ self
225
+ end
226
+
227
+ def inspect
228
+ "#{self.class.name.sub( /.*::/, '')}[ #{collect(&:inspect).join ', '}]"
229
+ end
230
+ def default() SmqlToAR::And end
231
+ def default_new( parent) default.new self, parent, false end
232
+ def collect_build_where
233
+ collect {|x| x.respond_to?( :build_where) ? x.build_where : x.to_s }
234
+ end
235
+ end
236
+
237
+ class And < SubBuilder
238
+ def default; SmqlToAR::Or; end
239
+ def build_where
240
+ collect_build_where.join ' AND '
241
+ end
242
+ end
243
+
244
+ class Or < SubBuilder
245
+ def build_where
246
+ collect_build_where.join ' OR '
247
+ end
248
+ end
162
249
  end
data/lib/smql_to_ar.rb CHANGED
@@ -15,6 +15,18 @@
15
15
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
 
17
17
  class SmqlToAR
18
+ module Assertion
19
+ def raise_unless cond, exception = nil, *args
20
+ cond, exception, *args = yield. cond, exception, *args if block_given?
21
+ raise exception || Exception, *args unless cond
22
+ end
23
+
24
+ def raise_if cond, exception = nil, *args
25
+ cond, exception, *args = yield. cond, exception, *args if block_given?
26
+ raise exception || Exception, *args if cond
27
+ end
28
+ end
29
+
18
30
  include ActiveSupport::Benchmarkable
19
31
  ############################################################################r
20
32
  # Exceptions
@@ -77,19 +89,27 @@ class SmqlToAR
77
89
  end
78
90
  end
79
91
 
80
- class OnlyOrderOnBaseError < SMQLError
92
+ class RootOnlyFunctionError < SMQLError
81
93
  def initialize path
82
94
  super :path => path
83
95
  end
84
96
  end
85
97
 
98
+ class ConColumnError < SMQLError
99
+ def initialize expected, got
100
+ super :expected => expected, :got => got
101
+ end
102
+ end
103
+
86
104
  class BuilderError < Exception; end
87
105
 
88
106
  #############################################################################
89
107
 
90
108
  # Model der Relation `rel` von `model`
91
109
  def self.model_of model, rel
92
- model.reflections[ rel.to_sym].andand.klass
110
+ rel = rel.to_sym
111
+ r = model.reflections[ rel].andand.klass
112
+ r.nil? && :self == rel ? model : r
93
113
  end
94
114
 
95
115
  # Eine Spalte in einer Tabelle, relativ zu `Column#model`.
@@ -103,7 +123,7 @@ class SmqlToAR
103
123
  def initialize model, *col
104
124
  @model = model
105
125
  @last_model = nil
106
- *@path, @col = Array.wrap( col).collect {|s| s.to_s.split /[.\/]/ }.flatten.collect &:to_sym
126
+ *@path, @col = *Array.wrap( col).collect( &it.to_s.split( /[.\/]/)).flatten.collect( &:to_sym).reject( &it==:self)
107
127
  end
108
128
 
109
129
  def last_model
@@ -113,9 +133,12 @@ class SmqlToAR
113
133
  def each
114
134
  model = @model
115
135
  @path.each do |rel|
116
- model = SmqlToAR.model_of model, rel
117
- return false unless model
118
- yield rel, model
136
+ rel = rel.to_sym
137
+ unless :self == rel
138
+ model = SmqlToAR.model_of model, rel
139
+ return false unless model
140
+ yield rel, model
141
+ end
119
142
  end
120
143
  model
121
144
  end
@@ -140,19 +163,23 @@ class SmqlToAR
140
163
  def joins builder = nil, table = nil, &exe
141
164
  pp = []
142
165
  table = Array.wrap table
143
- exe ||= builder ? lambda {|j, m| builder.join table+j, m} : Array.method( :[])
166
+ exe ||= builder ? lambda {|j, m| builder.joins table+j, m} : Array.method( :[])
144
167
  collect do |rel, model|
145
168
  pp.push rel
146
169
  exe.call pp, model
147
170
  end
148
171
  end
149
- def to_a() @path+[@col] end
172
+ def self?() !@col end
173
+ def length() @path.length+(self.self? ? 0 : 1) end
174
+ def size() @path.size+(self.self? ? 0 : 1) end
175
+ def to_a() @path+(self.self? ? [] : [@col]) end
150
176
  def to_s() to_a.join '.' end
151
177
  def to_sym() to_s.to_sym end
152
178
  def to_json() to_s end
153
179
  def inspect() "#<Column: #{model} #{to_s}>" end
154
- def relation() SmqlToAR.model_of last_model, @col end
180
+ def relation() self.self? ? model : SmqlToAR.model_of( last_model, @col) end
155
181
  def allowed?() ! self.protected? end
182
+ def child?() @path.empty? and !!relation end
156
183
  end
157
184
 
158
185
  attr_reader :model, :query, :conditions, :builder, :order
@@ -179,39 +206,64 @@ class SmqlToAR
179
206
  #p model: @model, query: @query
180
207
  end
181
208
 
209
+ def self.models models
210
+ models = Array.wrap models
211
+ r = Hash.new {|h,k| h[k] = {} }
212
+ while model = models.tap( &:uniq!).pop
213
+ refls = model.respond_to?( :reflections) && model.reflections
214
+ refls && refls.each do |name, refl|
215
+ r[model.name][name] = case refl
216
+ when ActiveRecord::Reflection::ThroughReflection then {:macro => refl.macro, :model => refl.klass.name, :through => refl.through_reflection.name}
217
+ when ActiveRecord::Reflection::AssociationReflection then {:macro => refl.macro, :model => refl.klass.name}
218
+ else raise "Ups: #{refl.class}"
219
+ end
220
+ models.push refl.klass unless r.keys.include? refl.klass.name
221
+ end
222
+ end
223
+ r
224
+ end
225
+
182
226
  def parse
183
- benchmark 'SMQL parse' do
227
+ #benchmark 'SMQL parse' do
184
228
  @conditions = ConditionTypes.try_parse @model, @query
185
- end
229
+ #end
186
230
  #p conditions: @conditions
187
231
  self
188
232
  end
189
233
 
190
234
  def build
191
- benchmark 'SMQL build query' do
235
+ #benchmark 'SMQL build query' do
192
236
  @builder = QueryBuilder.new @model
193
237
  table = @builder.base_table
194
238
  @conditions.each {|condition| condition.build builder, table }
195
- end
239
+ #end
196
240
  #p builder: @builder
197
241
  self
198
242
  end
199
243
 
200
244
  def ar
201
- @ar ||= benchmark 'SMQL ar' do
245
+ @ar ||= #benchmark 'SMQL ar' do
202
246
  @builder.to_ar
203
- end
247
+ #end
204
248
  end
205
249
 
206
250
  def to_ar
207
- benchmark 'SMQL' do
251
+ #benchmark 'SMQL' do
208
252
  parse
209
253
  build
210
254
  ar
211
- end
255
+ #end
212
256
  end
213
257
 
214
258
  def self.to_ar *params
215
259
  new( *params).to_ar
216
260
  end
261
+
262
+ def self.reload_library
263
+ lib_dir = File.dirname __FILE__
264
+ fj = lambda {|*a| File.join lib_dir, *a }
265
+ load fj.call( 'smql_to_ar.rb')
266
+ load fj.call( 'smql_to_ar', 'condition_types.rb')
267
+ load fj.call( 'smql_to_ar', 'query_builder.rb')
268
+ end
217
269
  end
metadata CHANGED
@@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version
6
6
  - 0
7
7
  - 0
8
8
  - 4
9
- - 2
10
- version: 0.0.4.2
9
+ - 3
10
+ version: 0.0.4.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Denis Knauf