smql 0.0.4.2 → 0.0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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