sequel_core 1.5.1 → 2.0.0

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