smql 0.0.3 → 0.0.4

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.3
1
+ 0.0.4
@@ -33,15 +33,29 @@ class SmqlToAR
33
33
  k.split( '|').collect &:to_sym
34
34
  end
35
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
+
36
53
  # Eine Regel parsen.
37
54
  # Ex: Person, "givenname=", "Peter"
38
55
  def try_parse_it model, colop, val
39
56
  r = nil
40
57
  #p :try_parse => { :model => model, :colop => colop, :value => val }
41
- constants.each do |c|
42
- next if :Condition == c
43
- c = const_get c
44
- next if Condition === c
58
+ conditions.each do |c|
45
59
  raise_unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/, UnexpectedColOpError.new( model, colop, val)
46
60
  col, op = $1, $2
47
61
  col = split_keys( col).collect {|c| Column.new model, c }
@@ -81,11 +95,17 @@ class SmqlToAR
81
95
  Expected = []
82
96
  Where = nil
83
97
 
84
- # Versuche das Objekt zu erkennen. Operator und Expected muessen passen.
85
- # Passt das Object, die Klasse instanzieren.
86
- def self.try_parse model, cols, op, val
87
- #p :self => name, :try_parse => op, :cols => cols, :with => self::Operator, :value => val, :expected => self::Expected, :model => model.name
88
- new model, cols, val if self::Operator === op and self::Expected.any?( &it === 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
89
109
  end
90
110
 
91
111
  def initialize model, cols, val
@@ -97,6 +117,10 @@ class SmqlToAR
97
117
  verify
98
118
  end
99
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
+
100
124
  def verify
101
125
  @cols.each do |col|
102
126
  verify_column col
@@ -118,8 +142,8 @@ class SmqlToAR
118
142
  end
119
143
 
120
144
  # Erstelle alle noetigen Klauseln. builder nimmt diese entgegen,
121
- # wobei builder.join, builder.select, builder.where und builder.wobs von interesse sind.
122
- # 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.
123
147
  # Ex:
124
148
  # 1) {"givenname=", "Peter"} #=> givenname = 'Peter'
125
149
  # 2) {"givenname=", ["Peter", "Hans"]} #=> ( givenname = 'Peter' OR givenname = 'Hans' )
@@ -132,11 +156,11 @@ class SmqlToAR
132
156
  @cols.each do |col|
133
157
  col.joins builder, table
134
158
  col = builder.column table+col.path, col.col
135
- 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 ] }
136
160
  end
137
161
  else
138
162
  values.keys.each do |vid|
139
- builder.where *@cols.collect {|col|
163
+ builder.where @cols.collect {|col|
140
164
  col.joins builder, table
141
165
  col = builder.column table+col.path, col.col
142
166
  self.class::Where % [ col, vid.to_s ]
@@ -189,19 +213,26 @@ class SmqlToAR
189
213
 
190
214
  In = simple_condition NotIn, '|=', '%s IN (%s)', [Array]
191
215
  In2 = simple_condition In, '', nil, [Array]
192
- NotEqual = simple_condition Condition, /\!=|<>/, "%s <> %s", [Array, String, Numeric]
216
+ NotEqual = simple_condition Condition, '!=', "%s <> %s", [Array, String, Numeric]
217
+ NotEqual2 = simple_condition Condition, '<>', "%s <> %s", [Array, String, Numeric]
193
218
  GreaterThanOrEqual = simple_condition Condition, '>=', "%s >= %s", [Array, Numeric]
194
219
  LesserThanOrEqual = simple_condition Condition, '<=', "%s <= %s", [Array, Numeric]
220
+
221
+ # Examples:
222
+ # { 'articles=>' => { id: 1 } }
223
+ # { 'articles=>' => [ { id: 1 }, { id: 2 } ] }
195
224
  class EqualJoin <Condition
196
- Operator = '='
197
- Expected = [Hash]
225
+ Operator = '=>'
226
+ Expected = [Hash, lambda {|x| x.kind_of?( Array) and x.all?( &it.kind_of?( Hash))}]
198
227
 
199
228
  def initialize *pars
200
229
  super( *pars)
230
+ @value = Array.wrap @value
201
231
  cols = {}
232
+ p self: self, cols: @cols
202
233
  @cols.each do |col|
203
234
  col_model = col.relation
204
- cols[col] = [col_model] + ConditionTypes.try_parse( col_model, @value)
235
+ cols[col] = [col_model] + @value.collect {|val| ConditionTypes.try_parse( col_model, val) }
205
236
  end
206
237
  @cols = cols
207
238
  end
@@ -212,11 +243,14 @@ class SmqlToAR
212
243
 
213
244
  def build builder, table
214
245
  @cols.each do |col, sub|
246
+ model, *sub = sub
215
247
  t = table + col.path + [col.col]
216
- col.joins.each {|j, m| builder.join table+j, m }
217
- builder.join t, sub[0]
218
- sub[1..-1].each &it.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 }
219
252
  end
253
+ ap '=>' => builder
220
254
  self
221
255
  end
222
256
  end
@@ -250,8 +284,7 @@ class SmqlToAR
250
284
  def build builder, table
251
285
  @cols.each do |col, sub|
252
286
  t = table+col.to_a
253
- p t: t, sub: sub
254
- builder.sub_join t, col, *sub[0..1]
287
+ builder.sub_joins t, col, *sub[0..1]
255
288
  sub[2..-1].each &it.build( builder, t)
256
289
  end
257
290
  self
@@ -264,8 +297,9 @@ class SmqlToAR
264
297
  LesserThan = simple_condition Condition, '<', "%s < %s", [Array, Numeric]
265
298
  NotIlike = simple_condition Condition, '!~', "%s NOT ILIKE %s", [Array, String]
266
299
  Ilike = simple_condition Condition, '~', "%s ILIKE %s", [Array, String]
300
+ Exists = simple_condition Condition, '', '%s IS NOT NULL', [true]
301
+ NotExists = simple_condition Condition, '', '%s IS NULL', [false]
267
302
 
268
- ####### No Operator #######
269
303
  Join = simple_condition EqualJoin, '', nil, [Hash]
270
304
  InRange2 = simple_condition InRange, '', nil, [Range]
271
305
  class Select < Condition
@@ -300,9 +334,15 @@ class SmqlToAR
300
334
  Expected = []
301
335
  attr_reader :model, :func, :args
302
336
 
303
- def self.try_parse model, func, args
304
- SmqlToAR.logger.info( { try_parse: [func,args]}.inspect)
305
- self.new model, func, args if self::Name === func and self::Expected.any?( &it === args)
337
+ class <<self
338
+ def try_parse model, func, args
339
+ SmqlToAR.logger.info( { try_parse: [func,args]}.inspect)
340
+ self.new model, func, args if self::Name === func and self::Expected.any?( &it === args)
341
+ end
342
+
343
+ def inspect
344
+ "#{self.name}(:name=>#{self::Name}, :expected=>#{self::Expected})"
345
+ end
306
346
  end
307
347
 
308
348
  def initialize model, func, args
@@ -29,30 +29,34 @@ class SmqlToAR
29
29
  end
30
30
 
31
31
  attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins, :prefix, :_vid
32
- attr_accessor :logger
32
+ attr_accessor :logger, :limit, :offset
33
33
 
34
- def initialize model, prefix = nil, base_table
34
+ def initialize model, prefix = nil
35
35
  @prefix = "smql"
36
36
  @logger = SmqlToAR.logger
37
37
  @table_alias = Hash.new do |h, k|
38
- k = Array.wrap k
39
- h[k] = "#{@prefix},#{k.join(',')}"
38
+ j = Array.wrap( k).compact
39
+ h[k] = h.key?(j) ? h[j] : "#{@prefix},#{j.join(',')}"
40
40
  end
41
- @_vid, @_where, @_wobs, @model, @quoter = 0, [], {}, model, model.connection
42
- @base_table = base_table.blank? ? [model.table_name.to_sym] : Array.wrap( base_table)
41
+ @_vid, @_where, @_wobs, @model, @quoter = 0, SmqlToAR::And[], {}, model, model.connection
42
+ @base_table = [model.table_name.to_sym]
43
43
  @table_alias[ @base_table] = @base_table.first
44
44
  t = quote_table_name @table_alias[ @base_table]
45
- @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [], [], []
45
+ @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [@base_table], [], []
46
46
  @table_model = {@base_table => @model}
47
47
  end
48
48
 
49
49
  def vid() Vid.new( @_vid+=1) end
50
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
+
51
55
  # Jede via where uebergebene Condition wird geodert und alle zusammen werden geundet.
52
56
  # "Konjunktive Normalform". Allerdings duerfen Conditions auch Komplexe Abfragen enthalten.
53
- # 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'])
54
58
  # #=> WHERE ( a = a OR b = c ) AND ( c = d OR e = e ) AND x = y ( ( m = n AND o = p ) OR f = g )
55
- def where *cond
59
+ def where cond
56
60
  @_where.push cond
57
61
  self
58
62
  end
@@ -75,28 +79,24 @@ class SmqlToAR
75
79
  end
76
80
 
77
81
  def build_join orig, pretable, table, prekey, key
78
- " JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} "
82
+ " LEFT OUTER JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} "
79
83
  end
80
84
 
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
+ 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})"
85
88
  end
86
89
 
87
90
  def join_ table, model, query, pretable = nil
88
- pp [:join_, table, model, query]
89
91
  pretable ||= table[0...-1]
90
92
  @table_model[ table] = model
91
93
  premodel = @table_model[ pretable]
92
94
  t = @table_alias[ table]
93
95
  pt = quote_table_name @table_alias[ table[ 0...-1]]
94
- pp premodel: premodel, table: table
95
96
  refl = premodel.reflections[table.last]
96
97
  case refl
97
98
  when ActiveRecord::Reflection::ThroughReflection
98
99
  through = refl.through_reflection
99
- pp refl: refl
100
100
  throughtable = table[0...-1]+[through.name.to_sym]
101
101
  srctable = throughtable+[refl.source_reflection.name]
102
102
  @table_model[ srctable] = model
@@ -120,7 +120,8 @@ class SmqlToAR
120
120
  self
121
121
  end
122
122
 
123
- def join table, model
123
+ def joins table, model
124
+ table = table.flatten.compact
124
125
  return self if @_joined.include? table # Already joined
125
126
  join_ table, model, quote_table_name( model.table_name)
126
127
  @_joined.push table
@@ -140,40 +141,22 @@ class SmqlToAR
140
141
  @_order.push "#{column table, col} #{:DESC == o ? :DESC : :ASC}"
141
142
  end
142
143
 
143
- def limit count
144
- @_limit = count
145
- end
146
-
147
- def offset count
148
- @_offset = count
149
- end
150
-
151
- class Dummy
152
- def method_missing m, *a, &e
153
- #p :dummy => m, :pars => a, :block => e
154
- self
155
- end
156
- end
157
-
158
144
  def build_ar
159
- where_str = @_where.collect do |w|
160
- w = Array.wrap w
161
- 1 == w.length ? w.first : "( #{w.join( ' OR ')} )"
162
- end.join ' AND '
145
+ where_str = @_where.type_correction!.optimize!.tap {|x| p x }.build_where
163
146
  incls = {}
164
147
  @_includes.each do |inc|
165
148
  b = incls
166
149
  inc[1..-1].collect {|rel| b = b[rel] ||= {} }
167
150
  end
168
- @logger.debug incls: incls, joins: @_joins
151
+ @logger.info where: where_str, wobs: @_wobs
169
152
  @model = @model.
170
153
  select( @_select.join( ', ')).
171
154
  joins( @_joins).
172
155
  where( where_str, @_wobs).
173
156
  order( @_order.join( ', ')).
174
157
  includes( incls)
175
- @model = @model.limit @_limit if @_limit
176
- @model = @model.offset @_offset if @_offset
158
+ @model = @model.limit @limit if @limit
159
+ @model = @model.offset @offset if @offset
177
160
  @model
178
161
  end
179
162
 
@@ -193,4 +176,73 @@ class SmqlToAR
193
176
  @model
194
177
  end
195
178
  end
179
+
180
+ class SubBuilder < Array
181
+ attr_reader :parent, :_where
182
+ delegate :wobs, :joins, :includes, :sub_joins, :vid, :quote_column_name, :quoter, :quote_table_name, :column, :to => :parent
183
+
184
+ def initialize parent, tmp = false
185
+ p init: self, parent: parent
186
+ @parent = parent
187
+ @parent.where self unless @parend.nil? && tmp
188
+ end
189
+
190
+ def new parent, tmp = false
191
+ super parent, tmp
192
+ #return parent if self.class == parent.class
193
+ #super parent
194
+ end
195
+
196
+ alias where push
197
+
198
+ def type_correction!
199
+ collect! do |sub|
200
+ if sub.kind_of? Array
201
+ sub = default[ *sub] unless sub.respond_to?( :type_correction!)
202
+ sub.type_correction!
203
+ end
204
+ sub
205
+ end
206
+ self
207
+ end
208
+
209
+ def optimize!
210
+ p optimize: self
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
+ p optimized: self
224
+ p ext: ext
225
+ push *ext
226
+ self
227
+ end
228
+
229
+ def inspect
230
+ "#{self.class.name.sub( /.*::/, '')}[ #{collect(&:inspect).join ', '}]"
231
+ end
232
+ def default() SmqlToAR::And end
233
+ def default_new( parent) default.new self, parent, false end
234
+ end
235
+
236
+ class And < SubBuilder
237
+ def default; SmqlToAR::Or; end
238
+ def build_where
239
+ join ' AND '
240
+ end
241
+ end
242
+
243
+ class Or < SubBuilder
244
+ def build_where
245
+ join ' OR '
246
+ end
247
+ end
196
248
  end
data/lib/smql_to_ar.rb CHANGED
@@ -107,7 +107,9 @@ class SmqlToAR
107
107
 
108
108
  # Model der Relation `rel` von `model`
109
109
  def self.model_of model, rel
110
- 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
111
113
  end
112
114
 
113
115
  # Eine Spalte in einer Tabelle, relativ zu `Column#model`.
@@ -121,7 +123,7 @@ class SmqlToAR
121
123
  def initialize model, *col
122
124
  @model = model
123
125
  @last_model = nil
124
- *@path, @col = Array.wrap( col).collect( &it.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)
125
127
  end
126
128
 
127
129
  def last_model
@@ -131,9 +133,12 @@ class SmqlToAR
131
133
  def each
132
134
  model = @model
133
135
  @path.each do |rel|
134
- model = SmqlToAR.model_of model, rel
135
- return false unless model
136
- 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
137
142
  end
138
143
  model
139
144
  end
@@ -158,20 +163,21 @@ class SmqlToAR
158
163
  def joins builder = nil, table = nil, &exe
159
164
  pp = []
160
165
  table = Array.wrap table
161
- 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( :[])
162
167
  collect do |rel, model|
163
168
  pp.push rel
164
169
  exe.call pp, model
165
170
  end
166
171
  end
167
- def length() @path.length+1 end
168
- def size() @path.size+1 end
169
- 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
170
176
  def to_s() to_a.join '.' end
171
177
  def to_sym() to_s.to_sym end
172
178
  def to_json() to_s end
173
179
  def inspect() "#<Column: #{model} #{to_s}>" end
174
- def relation() SmqlToAR.model_of last_model, @col end
180
+ def relation() self.self? ? model : SmqlToAR.model_of( last_model, @col) end
175
181
  def allowed?() ! self.protected? end
176
182
  def child?() @path.empty? and !!relation end
177
183
  end
@@ -225,9 +231,9 @@ class SmqlToAR
225
231
  self
226
232
  end
227
233
 
228
- def build prefix = nil, base_table = nil
234
+ def build prefix = nil
229
235
  benchmark 'SMQL build query' do
230
- @builder = QueryBuilder.new @model, prefix, base_table
236
+ @builder = QueryBuilder.new @model, prefix
231
237
  table = @builder.base_table
232
238
  @conditions.each &it.build( builder, table)
233
239
  end
@@ -252,4 +258,12 @@ class SmqlToAR
252
258
  def self.to_ar *params
253
259
  new( *params).to_ar
254
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
255
269
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 3
9
- version: 0.0.3
8
+ - 4
9
+ version: 0.0.4
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-27 00:00:00 +02:00
17
+ date: 2011-10-05 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency