smql 0.0.2 → 0.0.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.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