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
@@ -0,0 +1,331 @@
1
+ # This file includes dataset methods for translating Ruby expressions
2
+ # into SQL expressions, making it possible to specify dataset filters using
3
+ # blocks, e.g.:
4
+ #
5
+ # DB[:items].filter {:price < 100}
6
+ # DB[:items].filter {:category == 'ruby' && :date < Date.today - 7}
7
+ #
8
+ # Block filters can refer to literals, variables, constants, arguments,
9
+ # instance variables or anything else in order to create parameterized
10
+ # queries. Block filters can also refer to other dataset objects as
11
+ # sub-queries. Block filters are pretty much limitless!
12
+ #
13
+ # Block filters are based on ParseTree. If you do not have the ParseTree
14
+ # gem installed, block filters will raise an error.
15
+ #
16
+ # To enable full block filter support make sure you have both ParseTree and
17
+ # Ruby2Ruby installed:
18
+ #
19
+ # sudo gem install parsetree
20
+ # sudo gem install ruby2ruby
21
+
22
+ module Sequel
23
+ class Dataset
24
+ private
25
+ # Formats an comparison expression involving a left value and a right
26
+ # value. Comparison expressions differ according to the class of the right
27
+ # value. The stock implementation supports Range (inclusive and exclusive),
28
+ # Array (as a list of values to compare against), Dataset (as a subquery to
29
+ # compare against), or a regular value.
30
+ #
31
+ # dataset.compare_expr('id', 1..20) #=>
32
+ # "(id >= 1 AND id <= 20)"
33
+ # dataset.compare_expr('id', [3,6,10]) #=>
34
+ # "(id IN (3, 6, 10))"
35
+ # dataset.compare_expr('id', DB[:items].select(:id)) #=>
36
+ # "(id IN (SELECT id FROM items))"
37
+ # dataset.compare_expr('id', nil) #=>
38
+ # "(id IS NULL)"
39
+ # dataset.compare_expr('id', 3) #=>
40
+ # "(id = 3)"
41
+ def compare_expr(l, r)
42
+ case r
43
+ when Range
44
+ "(#{literal(l)} >= #{literal(r.begin)} AND #{literal(l)} <#{'=' unless r.exclude_end?} #{literal(r.end)})"
45
+ when Array, Sequel::Dataset
46
+ "(#{literal(l)} IN #{literal(r)})"
47
+ when NilClass
48
+ "(#{literal(l)} IS NULL)"
49
+ when Regexp
50
+ collate_match_expr(l, r)
51
+ else
52
+ "(#{literal(l)} = #{literal(r)})"
53
+ end
54
+ end
55
+
56
+ # Formats a string matching expression with support for multiple choices.
57
+ # For more information see #match_expr.
58
+ def collate_match_expr(l, r)
59
+ if r.is_a?(Array)
60
+ "(#{r.map {|i| match_expr(l, i)}.join(' OR ')})"
61
+ else
62
+ match_expr(l, r)
63
+ end
64
+ end
65
+
66
+ # Formats a string matching expression. The stock implementation supports
67
+ # matching against strings only using the LIKE operator. Specific adapters
68
+ # can override this method to provide support for regular expressions.
69
+ def match_expr(l, r)
70
+ case r
71
+ when String
72
+ "(#{literal(l)} LIKE #{literal(r)})"
73
+ else
74
+ raise Sequel::Error, "Unsupported match pattern class (#{r.class})."
75
+ end
76
+ end
77
+
78
+ # Translates a method call parse-tree to SQL expression. The following
79
+ # operators are recognized and translated to SQL expressions: >, <, >=, <=,
80
+ # ==, =~, +, -, *, /, %:
81
+ #
82
+ # :x == 1 #=> "(x = 1)"
83
+ # (:x + 100) < 200 #=> "((x + 100) < 200)"
84
+ #
85
+ # The in, in?, nil and nil? method calls are intercepted and passed to
86
+ # #compare_expr.
87
+ #
88
+ # :x.in [1, 2, 3] #=> "(x IN (1, 2, 3))"
89
+ # :x.in?(DB[:y].select(:z)) #=> "(x IN (SELECT z FROM y))"
90
+ # :x.nil? #=> "(x IS NULL)"
91
+ #
92
+ # The like and like? method calls are intercepted and passed to #match_expr.
93
+ #
94
+ # :x.like? 'ABC%' #=> "(x LIKE 'ABC%')"
95
+ #
96
+ # The method also supports SQL functions by invoking Symbol#[]:
97
+ #
98
+ # :avg[:x] #=> "avg(x)"
99
+ # :substring[:x, 5] #=> "substring(x, 5)"
100
+ #
101
+ # All other method calls are evaulated as normal Ruby code.
102
+ def call_expr(e, b, opts)
103
+ case op = e[2]
104
+ when :>, :<, :>=, :<=
105
+ l = eval_expr(e[1], b, opts)
106
+ r = eval_expr(e[3][1], b, opts)
107
+ if l.is_one_of?(Symbol, Sequel::LiteralString, Sequel::SQL::Expression) || \
108
+ r.is_one_of?(Symbol, Sequel::LiteralString, Sequel::SQL::Expression)
109
+ "(#{literal(l)} #{op} #{literal(r)})"
110
+ else
111
+ ext_expr(e, b, opts)
112
+ end
113
+ when :==
114
+ l = eval_expr(e[1], b, opts)
115
+ r = eval_expr(e[3][1], b, opts)
116
+ compare_expr(l, r)
117
+ when :=~
118
+ l = eval_expr(e[1], b, opts)
119
+ r = eval_expr(e[3][1], b, opts)
120
+ collate_match_expr(l, r)
121
+ when :+, :-, :*, :%, :/
122
+ l = eval_expr(e[1], b, opts)
123
+ r = eval_expr(e[3][1], b, opts)
124
+ if l.is_one_of?(Symbol, Sequel::LiteralString, Sequel::SQL::Expression) || \
125
+ r.is_one_of?(Symbol, Sequel::LiteralString, Sequel::SQL::Expression)
126
+ "(#{literal(l)} #{op} #{literal(r)})".lit
127
+ else
128
+ ext_expr(e, b, opts)
129
+ end
130
+ when :<<
131
+ l = eval_expr(e[1], b, opts)
132
+ r = eval_expr(e[3][1], b, opts)
133
+ "#{literal(l)} = #{literal(r)}".lit
134
+ when :|
135
+ l = eval_expr(e[1], b, opts)
136
+ r = eval_expr(e[3][1], b, opts)
137
+ if l.is_one_of?(Symbol, Sequel::SQL::Subscript)
138
+ l|r
139
+ else
140
+ ext_expr(e, b, opts)
141
+ end
142
+ when :in, :in?
143
+ # in/in? operators are supported using two forms:
144
+ # :x.in([1, 2, 3])
145
+ # :x.in(1, 2, 3) # variable arity
146
+ l = eval_expr(e[1], b, opts)
147
+ r = eval_expr((e[3].size == 2) ? e[3][1] : e[3], b, opts)
148
+ compare_expr(l, r)
149
+ when :nil, :nil?
150
+ l = eval_expr(e[1], b, opts)
151
+ compare_expr(l, nil)
152
+ when :like, :like?
153
+ l = eval_expr(e[1], b, opts)
154
+ r = eval_expr(e[3][1], b, opts)
155
+ collate_match_expr(l, r)
156
+ else
157
+ if (op == :[]) && (e[1][0] == :lit) && (Symbol === e[1][1])
158
+ # SQL Functions, e.g.: :sum[:x]
159
+ if e[3]
160
+ e[1][1][*eval_expr(e[3], b, opts)]
161
+ else
162
+ e[1][1][]
163
+ end
164
+ else
165
+ # external code
166
+ ext_expr(e, b, opts)
167
+ end
168
+ end
169
+ end
170
+
171
+ def fcall_expr(e, b, opts) #:nodoc:
172
+ ext_expr(e, b, opts)
173
+ end
174
+
175
+ def vcall_expr(e, b, opts) #:nodoc:
176
+ eval(e[1].to_s, b)
177
+ end
178
+
179
+ def iter_expr(e, b, opts) #:nodoc:
180
+ if e[1][0] == :call && e[1][2] == :each
181
+ unfold_each_expr(e, b, opts)
182
+ elsif e[1] == [:fcall, :proc]
183
+ eval_expr(e[3], b, opts) # inline proc
184
+ else
185
+ ext_expr(e, b, opts) # method call with inline proc
186
+ end
187
+ end
188
+
189
+ def replace_dvars(a, values)
190
+ a.map do |i|
191
+ if i.is_a?(Array) && (i[0] == :dvar)
192
+ if v = values[i[1]]
193
+ value_to_parse_tree(v)
194
+ else
195
+ i
196
+ end
197
+ elsif Array === i
198
+ replace_dvars(i, values)
199
+ else
200
+ i
201
+ end
202
+ end
203
+ end
204
+
205
+ def value_to_parse_tree(value)
206
+ c = Class.new
207
+ c.class_eval("def m; #{value.inspect}; end")
208
+ ParseTree.translate(c, :m)[2][1][2]
209
+ end
210
+
211
+ def unfold_each_expr(e, b, opts) #:nodoc:
212
+ source = eval_expr(e[1][1], b, opts)
213
+ block_dvars = []
214
+ if e[2][0] == :dasgn_curr
215
+ block_dvars << e[2][1]
216
+ elsif e[2][0] == :masgn
217
+ e[2][1].each do |i|
218
+ if i.is_a?(Array) && i[0] == :dasgn_curr
219
+ block_dvars << i[1]
220
+ end
221
+ end
222
+ end
223
+ new_block = [:block]
224
+
225
+ source.each do |*dvars|
226
+ iter_values = (Array === dvars[0]) ? dvars[0] : dvars
227
+ values = block_dvars.inject({}) {|m, i| m[i] = iter_values.shift; m}
228
+ iter = replace_dvars(e[3], values)
229
+ new_block << iter
230
+ end
231
+
232
+ pt_expr(new_block, b, opts)
233
+ end
234
+
235
+ # Evaluates a parse-tree into an SQL expression.
236
+ def eval_expr(e, b, opts)
237
+ case e[0]
238
+ when :call # method call
239
+ call_expr(e, b, opts)
240
+ when :fcall
241
+ fcall_expr(e, b, opts)
242
+ when :vcall
243
+ vcall_expr(e, b, opts)
244
+ when :ivar, :cvar, :dvar, :const, :gvar # local ref
245
+ eval(e[1].to_s, b)
246
+ when :nth_ref
247
+ eval("$#{e[1]}", b)
248
+ when :lvar # local context
249
+ if e[1] == :block
250
+ sub_proc = eval(e[1].to_s, b)
251
+ sub_proc.to_sql(self)
252
+ else
253
+ eval(e[1].to_s, b)
254
+ end
255
+ when :lit, :str # literal
256
+ e[1]
257
+ when :dot2 # inclusive range
258
+ eval_expr(e[1], b, opts)..eval_expr(e[2], b, opts)
259
+ when :dot3 # exclusive range
260
+ eval_expr(e[1], b, opts)...eval_expr(e[2], b, opts)
261
+ when :colon2 # qualified constant ref
262
+ eval_expr(e[1], b, opts).const_get(e[2])
263
+ when :false
264
+ false
265
+ when :true
266
+ true
267
+ when :nil
268
+ nil
269
+ when :array
270
+ # array
271
+ e[1..-1].map {|i| eval_expr(i, b, opts)}
272
+ when :match3
273
+ # =~/!~ operator
274
+ l = eval_expr(e[2], b, opts)
275
+ r = eval_expr(e[1], b, opts)
276
+ compare_expr(l, r)
277
+ when :iter
278
+ iter_expr(e, b, opts)
279
+ when :dasgn, :dasgn_curr
280
+ # assignment
281
+ l = e[1]
282
+ r = eval_expr(e[2], b, opts)
283
+ raise Sequel::Error::InvalidExpression, "#{l} = #{r}. Did you mean :#{l} == #{r}?"
284
+ when :if
285
+ op, c, br1, br2 = *e
286
+ if ext_expr(c, b, opts)
287
+ eval_expr(br1, b, opts)
288
+ elsif br2
289
+ eval_expr(br2, b, opts)
290
+ end
291
+ when :dstr
292
+ ext_expr(e, b, opts)
293
+ else
294
+ raise Sequel::Error::InvalidExpression, "Invalid expression tree: #{e.inspect}"
295
+ end
296
+ end
297
+
298
+ JOIN_AND = " AND ".freeze
299
+ JOIN_COMMA = ", ".freeze
300
+
301
+ def pt_expr(e, b, opts = {}) #:nodoc:
302
+ case e[0]
303
+ when :not # negation: !x, (x != y), (x !~ y)
304
+ if (e[1][0] == :lit) && (Symbol === e[1][1])
305
+ # translate (!:x) into (x = 'f')
306
+ compare_expr(e[1][1], false)
307
+ else
308
+ "(NOT #{pt_expr(e[1], b, opts)})"
309
+ end
310
+ when :and # x && y
311
+ "(#{e[1..-1].map {|i| pt_expr(i, b, opts)}.join(JOIN_AND)})"
312
+ when :or # x || y
313
+ "(#{pt_expr(e[1], b, opts)} OR #{pt_expr(e[2], b, opts)})"
314
+ when :call, :vcall, :iter, :match3, :if # method calls, blocks
315
+ eval_expr(e, b, opts)
316
+ when :block # block of statements
317
+ if opts[:comma_separated]
318
+ "#{e[1..-1].map {|i| pt_expr(i, b, opts)}.join(JOIN_COMMA)}"
319
+ else
320
+ "(#{e[1..-1].map {|i| pt_expr(i, b, opts)}.join(JOIN_AND)})"
321
+ end
322
+ else # literals
323
+ if e == [:lvar, :block]
324
+ eval_expr(e, b, opts)
325
+ else
326
+ literal(eval_expr(e, b, opts))
327
+ end
328
+ end
329
+ end
330
+ end
331
+ end
@@ -0,0 +1,41 @@
1
+ module Sequel
2
+ class Dataset
3
+ # Translates a query block into a dataset. Query blocks can be useful
4
+ # when expressing complex SELECT statements, e.g.:
5
+ #
6
+ # dataset = DB[:items].query do
7
+ # select :x, :y, :z
8
+ # filter((:x > 1) & (:y > 2))
9
+ # order :z.desc
10
+ # end
11
+ #
12
+ # Which is the same as:
13
+ #
14
+ # dataset = DB[:items].select(:x, :y, :z).filter((:x > 1) & (:y > 2)).order(:z.desc)
15
+ #
16
+ # Note that inside a call to query, you cannot call each, insert, update,
17
+ # or delete (or any method that calls those), or Sequel will raise an
18
+ # error.
19
+ def query(&block)
20
+ copy = clone({})
21
+ copy.extend(QueryBlockCopy)
22
+ copy.instance_eval(&block)
23
+ clone(copy.opts)
24
+ end
25
+
26
+ # Module used by Dataset#query that has the effect of making all
27
+ # dataset methods into !-style methods that modify the receiver.
28
+ module QueryBlockCopy
29
+ %w'each insert update delete'.each do |meth|
30
+ define_method(meth){|*args| raise Error, "##{meth} cannot be invoked inside a query block."}
31
+ end
32
+
33
+ # Merge the given options into the receiver's options and return the receiver
34
+ # instead of cloning the receiver.
35
+ def clone(opts = nil)
36
+ @opts.merge!(opts)
37
+ self
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,15 @@
1
+ module Sequel
2
+ class Dataset
3
+ # Creates a view in the database with the given named based
4
+ # on the current dataset.
5
+ def create_view(name)
6
+ @db.create_view(name, self)
7
+ end
8
+
9
+ # Creates or replaces a view in the database with the given
10
+ # named based on the current dataset.
11
+ def create_or_replace_view(name)
12
+ @db.create_or_replace_view(name, self)
13
+ end
14
+ end
15
+ end