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