sequel_core 1.5.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/CHANGELOG +116 -0
  2. data/COPYING +19 -19
  3. data/README +83 -32
  4. data/Rakefile +9 -20
  5. data/bin/sequel +43 -112
  6. data/doc/cheat_sheet.rdoc +225 -0
  7. data/doc/dataset_filtering.rdoc +257 -0
  8. data/lib/sequel_core/adapters/adapter_skeleton.rb +4 -2
  9. data/lib/sequel_core/adapters/ado.rb +3 -1
  10. data/lib/sequel_core/adapters/db2.rb +4 -2
  11. data/lib/sequel_core/adapters/dbi.rb +127 -113
  12. data/lib/sequel_core/adapters/informix.rb +4 -2
  13. data/lib/sequel_core/adapters/jdbc.rb +5 -3
  14. data/lib/sequel_core/adapters/mysql.rb +112 -46
  15. data/lib/sequel_core/adapters/odbc.rb +5 -7
  16. data/lib/sequel_core/adapters/odbc_mssql.rb +12 -3
  17. data/lib/sequel_core/adapters/openbase.rb +3 -1
  18. data/lib/sequel_core/adapters/oracle.rb +11 -9
  19. data/lib/sequel_core/adapters/postgres.rb +261 -262
  20. data/lib/sequel_core/adapters/sqlite.rb +72 -22
  21. data/lib/sequel_core/connection_pool.rb +140 -73
  22. data/lib/sequel_core/core_ext.rb +201 -66
  23. data/lib/sequel_core/core_sql.rb +123 -153
  24. data/lib/sequel_core/database/schema.rb +156 -0
  25. data/lib/sequel_core/database.rb +321 -338
  26. data/lib/sequel_core/dataset/callback.rb +11 -12
  27. data/lib/sequel_core/dataset/convenience.rb +213 -240
  28. data/lib/sequel_core/dataset/pagination.rb +58 -43
  29. data/lib/sequel_core/dataset/parse_tree_sequelizer.rb +331 -0
  30. data/lib/sequel_core/dataset/query.rb +41 -0
  31. data/lib/sequel_core/dataset/schema.rb +15 -0
  32. data/lib/sequel_core/dataset/sequelizer.rb +41 -373
  33. data/lib/sequel_core/dataset/sql.rb +741 -632
  34. data/lib/sequel_core/dataset.rb +183 -168
  35. data/lib/sequel_core/deprecated.rb +1 -169
  36. data/lib/sequel_core/exceptions.rb +24 -19
  37. data/lib/sequel_core/migration.rb +44 -52
  38. data/lib/sequel_core/object_graph.rb +43 -42
  39. data/lib/sequel_core/pretty_table.rb +71 -76
  40. data/lib/sequel_core/schema/generator.rb +163 -105
  41. data/lib/sequel_core/schema/sql.rb +250 -93
  42. data/lib/sequel_core/schema.rb +2 -8
  43. data/lib/sequel_core/sql.rb +394 -0
  44. data/lib/sequel_core/worker.rb +37 -27
  45. data/lib/sequel_core.rb +99 -45
  46. data/spec/adapters/informix_spec.rb +0 -1
  47. data/spec/adapters/mysql_spec.rb +177 -124
  48. data/spec/adapters/oracle_spec.rb +0 -1
  49. data/spec/adapters/postgres_spec.rb +98 -58
  50. data/spec/adapters/sqlite_spec.rb +45 -4
  51. data/spec/blockless_filters_spec.rb +269 -0
  52. data/spec/connection_pool_spec.rb +21 -18
  53. data/spec/core_ext_spec.rb +169 -19
  54. data/spec/core_sql_spec.rb +56 -49
  55. data/spec/database_spec.rb +78 -17
  56. data/spec/dataset_spec.rb +300 -428
  57. data/spec/migration_spec.rb +1 -1
  58. data/spec/object_graph_spec.rb +5 -11
  59. data/spec/rcov.opts +1 -1
  60. data/spec/schema_generator_spec.rb +16 -4
  61. data/spec/schema_spec.rb +89 -10
  62. data/spec/sequelizer_spec.rb +56 -56
  63. data/spec/spec.opts +0 -5
  64. data/spec/spec_config.rb +7 -0
  65. data/spec/spec_config.rb.example +5 -5
  66. data/spec/spec_helper.rb +6 -0
  67. data/spec/worker_spec.rb +1 -1
  68. metadata +78 -63
@@ -1,383 +1,51 @@
1
- class Sequel::Dataset
2
- # The Sequelizer module includes methods for translating Ruby expressions
3
- # into SQL expressions, making it possible to specify dataset filters using
4
- # blocks, e.g.:
5
- #
6
- # DB[:items].filter {:price < 100}
7
- # DB[:items].filter {:category == 'ruby' && :date < Date.today - 7}
8
- #
9
- # Block filters can refer to literals, variables, constants, arguments,
10
- # instance variables or anything else in order to create parameterized
11
- # queries. Block filters can also refer to other dataset objects as
12
- # sub-queries. Block filters are pretty much limitless!
13
- #
14
- # Block filters are based on ParseTree. If you do not have the ParseTree
15
- # gem installed, block filters will raise an error.
16
- #
17
- # To enable full block filter support make sure you have both ParseTree and
18
- # Ruby2Ruby installed:
19
- #
20
- # sudo gem install parsetree
21
- # sudo gem install ruby2ruby
22
- module Sequelizer
23
- private
24
- # Formats an comparison expression involving a left value and a right
25
- # value. Comparison expressions differ according to the class of the right
26
- # value. The stock implementation supports Range (inclusive and exclusive),
27
- # Array (as a list of values to compare against), Dataset (as a subquery to
28
- # compare against), or a regular value.
29
- #
30
- # dataset.compare_expr('id', 1..20) #=>
31
- # "(id >= 1 AND id <= 20)"
32
- # dataset.compare_expr('id', [3,6,10]) #=>
33
- # "(id IN (3, 6, 10))"
34
- # dataset.compare_expr('id', DB[:items].select(:id)) #=>
35
- # "(id IN (SELECT id FROM items))"
36
- # dataset.compare_expr('id', nil) #=>
37
- # "(id IS NULL)"
38
- # dataset.compare_expr('id', 3) #=>
39
- # "(id = 3)"
40
- def compare_expr(l, r)
41
- case r
42
- when Range
43
- r.exclude_end? ? \
44
- "(#{literal(l)} >= #{literal(r.begin)} AND #{literal(l)} < #{literal(r.end)})" : \
45
- "(#{literal(l)} >= #{literal(r.begin)} AND #{literal(l)} <= #{literal(r.end)})"
46
- when Array
47
- "(#{literal(l)} IN (#{literal(r)}))"
48
- when Sequel::Dataset
49
- "(#{literal(l)} IN (#{r.sql}))"
50
- when NilClass
51
- "(#{literal(l)} IS NULL)"
52
- when Regexp
53
- collate_match_expr(l, r)
54
- else
55
- "(#{literal(l)} = #{literal(r)})"
56
- end
57
- end
58
-
59
- # Formats a string matching expression with support for multiple choices.
60
- # For more information see #match_expr.
61
- def collate_match_expr(l, r)
62
- if r.is_a?(Array)
63
- "(#{r.map {|i| match_expr(l, i)}.join(' OR ')})"
64
- else
65
- match_expr(l, r)
66
- end
67
- end
68
-
69
- # Formats a string matching expression. The stock implementation supports
70
- # matching against strings only using the LIKE operator. Specific adapters
71
- # can override this method to provide support for regular expressions.
72
- def match_expr(l, r)
73
- case r
74
- when String
75
- "(#{literal(l)} LIKE #{literal(r)})"
76
- else
77
- raise Sequel::Error, "Unsupported match pattern class (#{r.class})."
78
- end
79
- end
80
-
81
- # Evaluates a method call. This method is used to evaluate Ruby expressions
82
- # referring to indirect values, e.g.:
83
- #
84
- # dataset.filter {:category => category.to_s}
85
- # dataset.filter {:x > y[0..3]}
86
- #
87
- # This method depends on the Ruby2Ruby gem. If you do not have Ruby2Ruby
88
- # installed, this method will raise an error.
89
- def ext_expr(e, b, opts)
90
- eval(RubyToRuby.new.process(e), b)
91
- end
92
-
93
- # Translates a method call parse-tree to SQL expression. The following
94
- # operators are recognized and translated to SQL expressions: >, <, >=, <=,
95
- # ==, =~, +, -, *, /, %:
96
- #
97
- # :x == 1 #=> "(x = 1)"
98
- # (:x + 100) < 200 #=> "((x + 100) < 200)"
99
- #
100
- # The in, in?, nil and nil? method calls are intercepted and passed to
101
- # #compare_expr.
102
- #
103
- # :x.in [1, 2, 3] #=> "(x IN (1, 2, 3))"
104
- # :x.in?(DB[:y].select(:z)) #=> "(x IN (SELECT z FROM y))"
105
- # :x.nil? #=> "(x IS NULL)"
106
- #
107
- # The like and like? method calls are intercepted and passed to #match_expr.
108
- #
109
- # :x.like? 'ABC%' #=> "(x LIKE 'ABC%')"
110
- #
111
- # The method also supports SQL functions by invoking Symbol#[]:
112
- #
113
- # :avg[:x] #=> "avg(x)"
114
- # :substring[:x, 5] #=> "substring(x, 5)"
115
- #
116
- # All other method calls are evaulated as normal Ruby code.
117
- def call_expr(e, b, opts)
118
- case op = e[2]
119
- when :>, :<, :>=, :<=
120
- l = eval_expr(e[1], b, opts)
121
- r = eval_expr(e[3][1], b, opts)
122
- if l.is_one_of?(Symbol, Sequel::LiteralString, Sequel::SQL::Expression) || \
123
- r.is_one_of?(Symbol, Sequel::LiteralString, Sequel::SQL::Expression)
124
- "(#{literal(l)} #{op} #{literal(r)})"
125
- else
126
- ext_expr(e, b, opts)
127
- end
128
- when :==
129
- l = eval_expr(e[1], b, opts)
130
- r = eval_expr(e[3][1], b, opts)
131
- compare_expr(l, r)
132
- when :=~
133
- l = eval_expr(e[1], b, opts)
134
- r = eval_expr(e[3][1], b, opts)
135
- collate_match_expr(l, r)
136
- when :+, :-, :*, :%, :/
137
- l = eval_expr(e[1], b, opts)
138
- r = eval_expr(e[3][1], b, opts)
139
- if l.is_one_of?(Symbol, Sequel::LiteralString, Sequel::SQL::Expression) || \
140
- r.is_one_of?(Symbol, Sequel::LiteralString, Sequel::SQL::Expression)
141
- "(#{literal(l)} #{op} #{literal(r)})".lit
142
- else
143
- ext_expr(e, b, opts)
144
- end
145
- when :<<
146
- l = eval_expr(e[1], b, opts)
147
- r = eval_expr(e[3][1], b, opts)
148
- "#{literal(l)} = #{literal(r)}".lit
149
- when :|
150
- l = eval_expr(e[1], b, opts)
151
- r = eval_expr(e[3][1], b, opts)
152
- if l.is_one_of?(Symbol, Sequel::SQL::Subscript)
153
- l|r
154
- else
155
- ext_expr(e, b, opts)
156
- end
157
- when :in, :in?
158
- # in/in? operators are supported using two forms:
159
- # :x.in([1, 2, 3])
160
- # :x.in(1, 2, 3) # variable arity
161
- l = eval_expr(e[1], b, opts)
162
- r = eval_expr((e[3].size == 2) ? e[3][1] : e[3], b, opts)
163
- compare_expr(l, r)
164
- when :nil, :nil?
165
- l = eval_expr(e[1], b, opts)
166
- compare_expr(l, nil)
167
- when :like, :like?
168
- l = eval_expr(e[1], b, opts)
169
- r = eval_expr(e[3][1], b, opts)
170
- collate_match_expr(l, r)
171
- else
172
- if (op == :[]) && (e[1][0] == :lit) && (Symbol === e[1][1])
173
- # SQL Functions, e.g.: :sum[:x]
174
- if e[3]
175
- e[1][1][*eval_expr(e[3], b, opts)]
176
- else
177
- e[1][1][]
178
- end
179
- else
180
- # external code
181
- ext_expr(e, b, opts)
182
- end
183
- end
184
- end
185
-
186
- def fcall_expr(e, b, opts) #:nodoc:
187
- ext_expr(e, b, opts)
188
- end
189
-
190
- def vcall_expr(e, b, opts) #:nodoc:
191
- eval(e[1].to_s, b)
192
- end
193
-
194
- def iter_expr(e, b, opts) #:nodoc:
195
- if e[1][0] == :call && e[1][2] == :each
196
- unfold_each_expr(e, b, opts)
197
- elsif e[1] == [:fcall, :proc]
198
- eval_expr(e[3], b, opts) # inline proc
199
- else
200
- ext_expr(e, b, opts) # method call with inline proc
201
- end
202
- end
203
-
204
- def replace_dvars(a, values)
205
- a.map do |i|
206
- if i.is_a?(Array) && (i[0] == :dvar)
207
- if v = values[i[1]]
208
- value_to_parse_tree(v)
209
- else
210
- i
211
- end
212
- elsif Array === i
213
- replace_dvars(i, values)
214
- else
215
- i
216
- end
217
- end
218
- end
219
-
220
- def value_to_parse_tree(value)
221
- c = Class.new
222
- c.class_eval("def m; #{value.inspect}; end")
223
- ParseTree.translate(c, :m)[2][1][2]
224
- end
225
-
226
- def unfold_each_expr(e, b, opts) #:nodoc:
227
- source = eval_expr(e[1][1], b, opts)
228
- block_dvars = []
229
- if e[2][0] == :dasgn_curr
230
- block_dvars << e[2][1]
231
- elsif e[2][0] == :masgn
232
- e[2][1].each do |i|
233
- if i.is_a?(Array) && i[0] == :dasgn_curr
234
- block_dvars << i[1]
235
- end
236
- end
237
- end
238
- new_block = [:block]
239
-
240
- source.each do |*dvars|
241
- iter_values = (Array === dvars[0]) ? dvars[0] : dvars
242
- values = block_dvars.inject({}) {|m, i| m[i] = iter_values.shift; m}
243
- iter = replace_dvars(e[3], values)
244
- new_block << iter
245
- end
246
-
247
- pt_expr(new_block, b, opts)
248
- end
249
-
250
- # Evaluates a parse-tree into an SQL expression.
251
- def eval_expr(e, b, opts)
252
- case e[0]
253
- when :call # method call
254
- call_expr(e, b, opts)
255
- when :fcall
256
- fcall_expr(e, b, opts)
257
- when :vcall
258
- vcall_expr(e, b, opts)
259
- when :ivar, :cvar, :dvar, :const, :gvar # local ref
260
- eval(e[1].to_s, b)
261
- when :nth_ref
262
- eval("$#{e[1]}", b)
263
- when :lvar # local context
264
- if e[1] == :block
265
- sub_proc = eval(e[1].to_s, b)
266
- sub_proc.to_sql(self)
267
- else
268
- eval(e[1].to_s, b)
269
- end
270
- when :lit, :str # literal
271
- e[1]
272
- when :dot2 # inclusive range
273
- eval_expr(e[1], b, opts)..eval_expr(e[2], b, opts)
274
- when :dot3 # exclusive range
275
- eval_expr(e[1], b, opts)...eval_expr(e[2], b, opts)
276
- when :colon2 # qualified constant ref
277
- eval_expr(e[1], b, opts).const_get(e[2])
278
- when :false
279
- false
280
- when :true
281
- true
282
- when :nil
283
- nil
284
- when :array
285
- # array
286
- e[1..-1].map {|i| eval_expr(i, b, opts)}
287
- when :match3
288
- # =~/!~ operator
289
- l = eval_expr(e[2], b, opts)
290
- r = eval_expr(e[1], b, opts)
291
- compare_expr(l, r)
292
- when :iter
293
- iter_expr(e, b, opts)
294
- when :dasgn, :dasgn_curr
295
- # assignment
296
- l = e[1]
297
- r = eval_expr(e[2], b, opts)
298
- raise Sequel::Error::InvalidExpression, "#{l} = #{r}. Did you mean :#{l} == #{r}?"
299
- when :if
300
- op, c, br1, br2 = *e
301
- if ext_expr(c, b, opts)
302
- eval_expr(br1, b, opts)
303
- elsif br2
304
- eval_expr(br2, b, opts)
305
- end
306
- when :dstr
307
- ext_expr(e, b, opts)
308
- else
309
- raise Sequel::Error::InvalidExpression, "Invalid expression tree: #{e.inspect}"
310
- end
1
+ begin
2
+ require 'parse_tree'
3
+ require 'sequel_core/dataset/parse_tree_sequelizer'
4
+ class Proc
5
+ def to_sql(dataset, opts = {})
6
+ dataset.send(:pt_expr, to_sexp[2], self.binding, opts)
311
7
  end
312
-
313
- JOIN_AND = " AND ".freeze
314
- JOIN_COMMA = ", ".freeze
315
-
316
- def pt_expr(e, b, opts = {}) #:nodoc:
317
- case e[0]
318
- when :not # negation: !x, (x != y), (x !~ y)
319
- if (e[1][0] == :lit) && (Symbol === e[1][1])
320
- # translate (!:x) into (x = 'f')
321
- compare_expr(e[1][1], false)
322
- else
323
- "(NOT #{pt_expr(e[1], b, opts)})"
324
- end
325
- when :and # x && y
326
- "(#{e[1..-1].map {|i| pt_expr(i, b, opts)}.join(JOIN_AND)})"
327
- when :or # x || y
328
- "(#{pt_expr(e[1], b, opts)} OR #{pt_expr(e[2], b, opts)})"
329
- when :call, :vcall, :iter, :match3, :if # method calls, blocks
330
- eval_expr(e, b, opts)
331
- when :block # block of statements
332
- if opts[:comma_separated]
333
- "#{e[1..-1].map {|i| pt_expr(i, b, opts)}.join(JOIN_COMMA)}"
334
- else
335
- "(#{e[1..-1].map {|i| pt_expr(i, b, opts)}.join(JOIN_AND)})"
336
- end
337
- else # literals
338
- if e == [:lvar, :block]
339
- eval_expr(e, b, opts)
340
- else
341
- literal(eval_expr(e, b, opts))
342
- end
8
+ end
9
+ begin
10
+ require 'ruby2ruby'
11
+ class Sequel::Dataset
12
+ # Evaluates a method call. This method is used to evaluate Ruby expressions
13
+ # referring to indirect values, e.g.:
14
+ #
15
+ # dataset.filter {:category => category.to_s}
16
+ # dataset.filter {:x > y[0..3]}
17
+ #
18
+ # This method depends on the Ruby2Ruby gem. If you do not have Ruby2Ruby
19
+ # installed, this method will raise an error.
20
+ def ext_expr(e, b, opts)
21
+ eval(::RubyToRuby.new.process(e), b)
22
+ end
23
+ end
24
+ class Proc
25
+ remove_method :to_sexp
26
+ end
27
+ rescue LoadError
28
+ class Sequel::Dataset
29
+ def ext_expr(*args)
30
+ raise Sequel::Error, "You must have the Ruby2Ruby gem installed in order to use this block filter."
31
+ end
32
+ end
33
+ ensure
34
+ class Proc
35
+ # replacement for Proc#to_sexp as defined in ruby2ruby.
36
+ # see also: http://rubyforge.org/tracker/index.php?func=detail&aid=18095&group_id=1513&atid=5921
37
+ # The ruby2ruby implementation leaks memory, so we fix it.
38
+ def to_sexp
39
+ block = self
40
+ c = Class.new {define_method(:m, &block)}
41
+ ParseTree.translate(c, :m)[2]
343
42
  end
344
43
  end
345
44
  end
346
- end
347
-
348
- class Proc
349
- def to_sql(dataset, opts = {})
350
- dataset.send(:pt_expr, to_sexp[2], self.binding, opts)
351
- end
352
- end
353
-
354
- begin
355
- require 'parse_tree'
356
- rescue Exception
45
+ rescue LoadError
357
46
  class Proc
358
47
  def to_sql(*args)
359
48
  raise Sequel::Error, "You must have the ParseTree gem installed in order to use block filters."
360
49
  end
361
50
  end
362
51
  end
363
-
364
- begin
365
- require 'ruby2ruby'
366
- rescue Exception
367
- module Sequel::Dataset::Sequelizer
368
- def ext_expr(*args)
369
- raise Sequel::Error, "You must have the Ruby2Ruby gem installed in order to use this block filter."
370
- end
371
- end
372
- end
373
-
374
- class Proc
375
- # replacement for Proc#to_sexp as defined in ruby2ruby.
376
- # see also: http://rubyforge.org/tracker/index.php?func=detail&aid=18095&group_id=1513&atid=5921
377
- # The ruby2ruby implementation leaks memory, so we fix it.
378
- def to_sexp
379
- block = self
380
- c = Class.new {define_method(:m, &block)}
381
- ParseTree.translate(c, :m)[2]
382
- end
383
- end