smql 0.0.3 → 0.0.4

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.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