smql 0.0.2 → 0.0.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.3
@@ -25,6 +25,8 @@ 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
@@ -40,13 +42,13 @@ class SmqlToAR
40
42
  next if :Condition == c
41
43
  c = const_get c
42
44
  next if Condition === c
43
- raise UnexpectedColOpError.new( model, colop, val) unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/
45
+ raise_unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/, UnexpectedColOpError.new( model, colop, val)
44
46
  col, op = $1, $2
45
47
  col = split_keys( col).collect {|c| Column.new model, c }
46
48
  r = c.try_parse model, col, op, val
47
49
  break if r
48
50
  end
49
- raise UnexpectedError.new( model, colop, val) unless r
51
+ raise_unless r, UnexpectedError.new( model, colop, val)
50
52
  r
51
53
  end
52
54
 
@@ -72,6 +74,8 @@ class SmqlToAR
72
74
  end
73
75
 
74
76
  class Condition
77
+ include SmqlToAR::Assertion
78
+ extend SmqlToAR::Assertion
75
79
  attr_reader :value, :cols
76
80
  Operator = nil
77
81
  Expected = []
@@ -81,7 +85,7 @@ class SmqlToAR
81
85
  # Passt das Object, die Klasse instanzieren.
82
86
  def self.try_parse model, cols, op, val
83
87
  #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 }
88
+ new model, cols, val if self::Operator === op and self::Expected.any?( &it === val)
85
89
  end
86
90
 
87
91
  def initialize model, cols, val
@@ -103,14 +107,14 @@ class SmqlToAR
103
107
  # Gibt es eine Spalte diesen Namens?
104
108
  # Oder: Gibt es eine Relation diesen Namens? (Hier nicht der Fall)
105
109
  def verify_column col
106
- raise NonExistingColumnError.new( %w[Column], col) unless col.exist_in?
110
+ raise_unless col.exist_in?, NonExistingColumnError.new( %w[Column], col)
107
111
  end
108
112
 
109
113
  # Modelle koennen Spalten/Relationen verbieten mit Model#smql_protected.
110
114
  # Dieses muss ein Object mit #include?( name_als_string) zurueckliefern,
111
115
  # welches true fuer verboten und false fuer, erlaubt steht.
112
116
  def verify_allowed col
113
- raise ProtectedColumnError.new( col) if col.protected?
117
+ raise_if col.protected?, ProtectedColumnError.new( col)
114
118
  end
115
119
 
116
120
  # Erstelle alle noetigen Klauseln. builder nimmt diese entgegen,
@@ -196,31 +200,64 @@ class SmqlToAR
196
200
  super( *pars)
197
201
  cols = {}
198
202
  @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
203
+ col_model = col.relation
201
204
  cols[col] = [col_model] + ConditionTypes.try_parse( col_model, @value)
202
205
  end
203
206
  @cols = cols
204
207
  end
205
208
 
206
209
  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
210
+ raise_unless col.relation, NonExistingRelationError.new( %w[Relation], col)
210
211
  end
211
212
 
212
213
  def build builder, table
213
214
  @cols.each do |col, sub|
214
215
  t = table + col.path + [col.col]
215
- #p sub: sub
216
- p col: col, joins: col.joins
217
216
  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 }
217
+ builder.join t, sub[0]
218
+ sub[1..-1].each &it.build( builder, t)
219
+ end
220
+ self
221
+ end
222
+ end
223
+
224
+ # Takes to Queries.
225
+ # First Query will be a Subquery, second a regular query.
226
+ # Example:
227
+ # Person.smql 'sub.articles:' => [{'limit:' => 1, 'order:': 'updated_at desc'}, {'content~' => 'some text'}]
228
+ # Person must have as last Article (compared by updated_at) owned by Person a Artive which has 'some text' in content.
229
+ # The last Article needn't to have 'some text' has content, the subquery takes it anyway.
230
+ # But the second query compares to it and never to any other Article, because these are filtered by first query.
231
+ # The difference to
232
+ # Person.smql :articles => {'content~' => 'some text', 'limit:' => 1, 'order:': 'updated_at desc'}
233
+ # is, second is not allowed (limit and order must be in root) and this means something like
234
+ # "Person must have the Article owned by Person which has 'some text' in content.
235
+ # limit and order has no function in this query and this article needn't to be the last."
236
+ class SubEqualJoin < EqualJoin
237
+ Operator = '()'
238
+ Expected = [lambda {|x| x.kind_of?( Array) and (1..2).include?( x.length) and x.all?( &it.kind_of?( Hash))}]
239
+
240
+ def initialize model, cols, val
241
+ super model, cols, val[1]
242
+ # sub: model, subquery, sub(condition)
243
+ @cols.each {|col, sub| sub[ 1...1] = SmqlToAR.new( col.relation, val[0]).parse }
244
+ end
245
+
246
+ def verify_column col
247
+ raise_unless col.child?, ConColumnError.new( [:Column], col)
248
+ end
249
+
250
+ def build builder, table
251
+ @cols.each do |col, sub|
252
+ t = table+col.to_a
253
+ p t: t, sub: sub
254
+ builder.sub_join t, col, *sub[0..1]
255
+ sub[2..-1].each &it.build( builder, t)
220
256
  end
221
257
  self
222
258
  end
223
259
  end
260
+
224
261
  Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric]
225
262
  Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric]
226
263
  GreaterThan = simple_condition Condition, '>', "%s > %s", [Array, Numeric]
@@ -236,7 +273,7 @@ class SmqlToAR
236
273
  Expected = [nil]
237
274
 
238
275
  def verify_column col
239
- raise NonExistingSelectableError.new( col) unless col.exist_in? or SmqlToAR.model_of( col.last_model, col.col)
276
+ raise_unless col.exist_in? || SmqlToAR.model_of( col.last_model, col.col), NonExistingSelectableError.new( col)
240
277
  end
241
278
 
242
279
  def build builder, table
@@ -258,13 +295,14 @@ class SmqlToAR
258
295
  Expected = [String, Array, Hash, Numeric, nil]
259
296
 
260
297
  class Function
298
+ include SmqlToAR::Assertion
261
299
  Name = nil
262
300
  Expected = []
263
301
  attr_reader :model, :func, :args
264
302
 
265
303
  def self.try_parse model, func, args
266
304
  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 }
305
+ self.new model, func, args if self::Name === func and self::Expected.any?( &it === args)
268
306
  end
269
307
 
270
308
  def initialize model, func, args
@@ -277,22 +315,19 @@ class SmqlToAR
277
315
  Expected = [String, Array, Hash, nil]
278
316
 
279
317
  def initialize model, func, args
280
- SmqlToAR.logger.info( {args: args}.inspect)
281
318
  args = case args
282
319
  when String then [args]
283
320
  when Array, Hash then args.to_a
284
321
  when nil then nil
285
322
  else raise 'Oops'
286
323
  end
287
- SmqlToAR.logger.info( {args: args}.inspect)
288
324
  args.andand.collect! do |o|
289
325
  o = Array.wrap o
290
326
  col = Column.new model, o.first
291
327
  o = 'desc' == o.last.to_s.downcase ? :DESC : :ASC
292
- raise NonExistingColumnError.new( [:Column], col) unless col.exist_in?
328
+ raise_unless col.exist_in?, NonExistingColumnError.new( [:Column], col)
293
329
  [col, o]
294
330
  end
295
- SmqlToAR.logger.info( {args: args}.inspect)
296
331
  super model, func, args
297
332
  end
298
333
 
@@ -302,12 +337,32 @@ class SmqlToAR
302
337
  col, o = o
303
338
  col.joins builder, table
304
339
  t = table + col.path
305
- raise OnlyOrderOnBaseError.new( t) unless 1 == t.length
340
+ raise_unless 1 == t.length, RootOnlyFunctionError.new( t)
306
341
  builder.order t, col.col, o
307
342
  end
308
343
  end
309
344
  end
310
345
 
346
+ class Limit < Function
347
+ Name = :limit
348
+ Expected = [Fixnum]
349
+
350
+ def build builder, table
351
+ raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
352
+ builder.limit @args
353
+ end
354
+ end
355
+
356
+ class Offset < Function
357
+ Name = :offset
358
+ Expected = [Fixnum]
359
+
360
+ def build builder, table
361
+ raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
362
+ builder.offset @args
363
+ end
364
+ end
365
+
311
366
  def self.new model, col, val
312
367
  SmqlToAR.logger.info( { function: col.first.to_sym }.inspect)
313
368
  r = nil
@@ -315,9 +370,7 @@ class SmqlToAR
315
370
  next if [:Function, :Where, :Expected, :Operator].include? c
316
371
  c = const_get c
317
372
  next if Function === c or not c.respond_to?( :try_parse)
318
- SmqlToAR.logger.info( {f: c}.inspect)
319
373
  r = c.try_parse model, col.first.to_sym, val
320
- SmqlToAR.logger.info( {r: r}.inspect)
321
374
  break if r
322
375
  end
323
376
  r
@@ -22,23 +22,24 @@ 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
31
+ attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins, :prefix, :_vid
32
32
  attr_accessor :logger
33
33
 
34
- def initialize model
34
+ def initialize model, prefix = nil, base_table
35
+ @prefix = "smql"
35
36
  @logger = SmqlToAR.logger
36
37
  @table_alias = Hash.new do |h, k|
37
38
  k = Array.wrap k
38
- h[k] = "smql,#{k.join(',')}"
39
+ h[k] = "#{@prefix},#{k.join(',')}"
39
40
  end
40
41
  @_vid, @_where, @_wobs, @model, @quoter = 0, [], {}, model, model.connection
41
- @base_table = [model.table_name.to_sym]
42
+ @base_table = base_table.blank? ? [model.table_name.to_sym] : Array.wrap( base_table)
42
43
  @table_alias[ @base_table] = @base_table.first
43
44
  t = quote_table_name @table_alias[ @base_table]
44
45
  @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [], [], []
@@ -74,32 +75,57 @@ class SmqlToAR
74
75
  end
75
76
 
76
77
  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} "
78
+ " JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} "
78
79
  end
79
80
 
80
- def join table, model
81
- return self if @_joined.include? table # Already joined
82
- pretable = table[0...-1]
81
+ def sub_join table, col, model, query
82
+ pp [:sub_join, table, col. model, query]
83
+ prefix, base_table = "#{@prefix}_sub", col.col
84
+ join_ table, model, "(#{query.build( prefix, base_table).tap{|q| p :sub_join => q }.ar.to_sql})"
85
+ end
86
+
87
+ def join_ table, model, query, pretable = nil
88
+ pp [:join_, table, model, query]
89
+ pretable ||= table[0...-1]
83
90
  @table_model[ table] = model
84
91
  premodel = @table_model[ pretable]
85
92
  t = @table_alias[ table]
86
93
  pt = quote_table_name @table_alias[ table[ 0...-1]]
94
+ pp premodel: premodel, table: table
87
95
  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}"
96
+ case refl
97
+ when ActiveRecord::Reflection::ThroughReflection
98
+ through = refl.through_reflection
99
+ pp refl: refl
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
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 join table, model
124
+ return self if @_joined.include? table # Already joined
125
+ join_ table, model, quote_table_name( model.table_name)
126
+ @_joined.push table
127
+ end
128
+
103
129
  def includes table
104
130
  @_includes.push table
105
131
  self
@@ -114,6 +140,14 @@ class SmqlToAR
114
140
  @_order.push "#{column table, col} #{:DESC == o ? :DESC : :ASC}"
115
141
  end
116
142
 
143
+ def limit count
144
+ @_limit = count
145
+ end
146
+
147
+ def offset count
148
+ @_offset = count
149
+ end
150
+
117
151
  class Dummy
118
152
  def method_missing m, *a, &e
119
153
  #p :dummy => m, :pars => a, :block => e
@@ -138,6 +172,9 @@ class SmqlToAR
138
172
  where( where_str, @_wobs).
139
173
  order( @_order.join( ', ')).
140
174
  includes( incls)
175
+ @model = @model.limit @_limit if @_limit
176
+ @model = @model.offset @_offset if @_offset
177
+ @model
141
178
  end
142
179
 
143
180
  def fix_calculate
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,12 +89,18 @@ 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
  #############################################################################
@@ -103,7 +121,7 @@ class SmqlToAR
103
121
  def initialize model, *col
104
122
  @model = model
105
123
  @last_model = nil
106
- *@path, @col = Array.wrap( col).collect {|s| s.to_s.split /[.\/]/ }.flatten.collect &:to_sym
124
+ *@path, @col = Array.wrap( col).collect( &it.to_s.split( /[.\/]/)).flatten.collect( &:to_sym)
107
125
  end
108
126
 
109
127
  def last_model
@@ -146,6 +164,8 @@ class SmqlToAR
146
164
  exe.call pp, model
147
165
  end
148
166
  end
167
+ def length() @path.length+1 end
168
+ def size() @path.size+1 end
149
169
  def to_a() @path+[@col] end
150
170
  def to_s() to_a.join '.' end
151
171
  def to_sym() to_s.to_sym end
@@ -153,6 +173,7 @@ class SmqlToAR
153
173
  def inspect() "#<Column: #{model} #{to_s}>" end
154
174
  def relation() SmqlToAR.model_of last_model, @col end
155
175
  def allowed?() ! self.protected? end
176
+ def child?() @path.empty? and !!relation end
156
177
  end
157
178
 
158
179
  attr_reader :model, :query, :conditions, :builder, :order
@@ -179,6 +200,23 @@ class SmqlToAR
179
200
  #p model: @model, query: @query
180
201
  end
181
202
 
203
+ def self.models models
204
+ models = Array.wrap models
205
+ r = Hash.new {|h,k| h[k] = {} }
206
+ while model = models.tap( &:uniq!).pop
207
+ refls = model.respond_to?( :reflections) && model.reflections
208
+ refls && refls.each do |name, refl|
209
+ r[model.name][name] = case refl
210
+ when ActiveRecord::Reflection::ThroughReflection then {:macro => refl.macro, :model => refl.klass.name, :through => refl.through_reflection.name}
211
+ when ActiveRecord::Reflection::AssociationReflection then {:macro => refl.macro, :model => refl.klass.name}
212
+ else raise "Ups: #{refl.class}"
213
+ end
214
+ models.push refl.klass unless r.keys.include? refl.klass.name
215
+ end
216
+ end
217
+ r
218
+ end
219
+
182
220
  def parse
183
221
  benchmark 'SMQL parse' do
184
222
  @conditions = ConditionTypes.try_parse @model, @query
@@ -187,11 +225,11 @@ class SmqlToAR
187
225
  self
188
226
  end
189
227
 
190
- def build
228
+ def build prefix = nil, base_table = nil
191
229
  benchmark 'SMQL build query' do
192
- @builder = QueryBuilder.new @model
230
+ @builder = QueryBuilder.new @model, prefix, base_table
193
231
  table = @builder.base_table
194
- @conditions.each {|condition| condition.build builder, table }
232
+ @conditions.each &it.build( builder, table)
195
233
  end
196
234
  #p builder: @builder
197
235
  self
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 2
9
- version: 0.0.2
8
+ - 3
9
+ version: 0.0.3
10
10
  platform: ruby
11
11
  authors:
12
12
  - Denis Knauf
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-09-16 00:00:00 +02:00
17
+ date: 2011-09-27 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -56,6 +56,19 @@ dependencies:
56
56
  version: "0"
57
57
  type: :runtime
58
58
  version_requirements: *id003
59
+ - !ruby/object:Gem::Dependency
60
+ name: methodphitamine
61
+ prerelease: false
62
+ requirement: &id004 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ type: :runtime
71
+ version_requirements: *id004
59
72
  description: SMQL is a JSON-based query langauage similar to MQL. This gem convertes these querys to ActiveRecord.
60
73
  email:
61
74
  - Denis.Knauf@gmail.com