sequel 0.5.0.2 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/COPYING +18 -18
  2. data/Rakefile +17 -98
  3. data/lib/sequel.rb +2 -71
  4. metadata +10 -108
  5. data/CHANGELOG +0 -989
  6. data/bin/sequel +0 -41
  7. data/lib/sequel/adapters/adapter_skeleton.rb +0 -68
  8. data/lib/sequel/adapters/ado.rb +0 -100
  9. data/lib/sequel/adapters/db2.rb +0 -158
  10. data/lib/sequel/adapters/dbi.rb +0 -126
  11. data/lib/sequel/adapters/informix.rb +0 -87
  12. data/lib/sequel/adapters/jdbc.rb +0 -108
  13. data/lib/sequel/adapters/mysql.rb +0 -269
  14. data/lib/sequel/adapters/odbc.rb +0 -145
  15. data/lib/sequel/adapters/odbc_mssql.rb +0 -93
  16. data/lib/sequel/adapters/openbase.rb +0 -90
  17. data/lib/sequel/adapters/oracle.rb +0 -99
  18. data/lib/sequel/adapters/postgres.rb +0 -519
  19. data/lib/sequel/adapters/sqlite.rb +0 -192
  20. data/lib/sequel/ado.rb +0 -6
  21. data/lib/sequel/array_keys.rb +0 -296
  22. data/lib/sequel/connection_pool.rb +0 -152
  23. data/lib/sequel/core_ext.rb +0 -59
  24. data/lib/sequel/core_sql.rb +0 -191
  25. data/lib/sequel/database.rb +0 -433
  26. data/lib/sequel/dataset.rb +0 -409
  27. data/lib/sequel/dataset/convenience.rb +0 -321
  28. data/lib/sequel/dataset/sequelizer.rb +0 -354
  29. data/lib/sequel/dataset/sql.rb +0 -586
  30. data/lib/sequel/db2.rb +0 -6
  31. data/lib/sequel/dbi.rb +0 -6
  32. data/lib/sequel/exceptions.rb +0 -45
  33. data/lib/sequel/informix.rb +0 -6
  34. data/lib/sequel/migration.rb +0 -191
  35. data/lib/sequel/model.rb +0 -8
  36. data/lib/sequel/mysql.rb +0 -6
  37. data/lib/sequel/odbc.rb +0 -6
  38. data/lib/sequel/oracle.rb +0 -6
  39. data/lib/sequel/postgres.rb +0 -6
  40. data/lib/sequel/pretty_table.rb +0 -73
  41. data/lib/sequel/schema.rb +0 -8
  42. data/lib/sequel/schema/schema_generator.rb +0 -131
  43. data/lib/sequel/schema/schema_sql.rb +0 -131
  44. data/lib/sequel/sqlite.rb +0 -6
  45. data/lib/sequel/worker.rb +0 -58
  46. data/spec/adapters/informix_spec.rb +0 -139
  47. data/spec/adapters/mysql_spec.rb +0 -330
  48. data/spec/adapters/oracle_spec.rb +0 -130
  49. data/spec/adapters/postgres_spec.rb +0 -189
  50. data/spec/adapters/sqlite_spec.rb +0 -345
  51. data/spec/array_keys_spec.rb +0 -679
  52. data/spec/connection_pool_spec.rb +0 -356
  53. data/spec/core_ext_spec.rb +0 -67
  54. data/spec/core_sql_spec.rb +0 -301
  55. data/spec/database_spec.rb +0 -811
  56. data/spec/dataset_spec.rb +0 -2381
  57. data/spec/migration_spec.rb +0 -261
  58. data/spec/pretty_table_spec.rb +0 -66
  59. data/spec/rcov.opts +0 -4
  60. data/spec/schema_generator_spec.rb +0 -86
  61. data/spec/schema_spec.rb +0 -230
  62. data/spec/sequel_spec.rb +0 -10
  63. data/spec/sequelizer_spec.rb +0 -389
  64. data/spec/spec.opts +0 -5
  65. data/spec/spec_helper.rb +0 -44
  66. data/spec/worker_spec.rb +0 -96
@@ -1,354 +0,0 @@
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 < 3.days.ago}
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
- # Formats an comparison expression involving a left value and a right
24
- # value. Comparison expressions differ according to the class of the right
25
- # value. The stock implementation supports Range (inclusive and exclusive),
26
- # Array (as a list of values to compare against), Dataset (as a subquery to
27
- # compare against), or a regular value.
28
- #
29
- # dataset.compare_expr('id', 1..20) #=>
30
- # "(id >= 1 AND id <= 20)"
31
- # dataset.compare_expr('id', [3,6,10]) #=>
32
- # "(id IN (3, 6, 10))"
33
- # dataset.compare_expr('id', DB[:items].select(:id)) #=>
34
- # "(id IN (SELECT id FROM items))"
35
- # dataset.compare_expr('id', nil) #=>
36
- # "(id IS NULL)"
37
- # dataset.compare_expr('id', 3) #=>
38
- # "(id = 3)"
39
- def compare_expr(l, r)
40
- case r
41
- when Range
42
- r.exclude_end? ? \
43
- "(#{literal(l)} >= #{literal(r.begin)} AND #{literal(l)} < #{literal(r.end)})" : \
44
- "(#{literal(l)} >= #{literal(r.begin)} AND #{literal(l)} <= #{literal(r.end)})"
45
- when Array
46
- "(#{literal(l)} IN (#{literal(r)}))"
47
- when Sequel::Dataset
48
- "(#{literal(l)} IN (#{r.sql}))"
49
- when NilClass
50
- "(#{literal(l)} IS NULL)"
51
- when Regexp
52
- match_expr(l, r)
53
- else
54
- "(#{literal(l)} = #{literal(r)})"
55
- end
56
- end
57
-
58
- # Formats a string matching expression. The stock implementation supports
59
- # matching against strings only using the LIKE operator. Specific adapters
60
- # can override this method to provide support for regular expressions.
61
- def match_expr(l, r)
62
- case r
63
- when String
64
- "(#{literal(l)} LIKE #{literal(r)})"
65
- else
66
- raise Sequel::Error, "Unsupported match pattern class (#{r.class})."
67
- end
68
- end
69
-
70
- # Evaluates a method call. This method is used to evaluate Ruby expressions
71
- # referring to indirect values, e.g.:
72
- #
73
- # dataset.filter {:category => category.to_s}
74
- # dataset.filter {:x > y[0..3]}
75
- #
76
- # This method depends on the Ruby2Ruby gem. If you do not have Ruby2Ruby
77
- # installed, this method will raise an error.
78
- def ext_expr(e, b, opts)
79
- eval(RubyToRuby.new.process(e), b)
80
- end
81
-
82
- # Translates a method call parse-tree to SQL expression. The following
83
- # operators are recognized and translated to SQL expressions: >, <, >=, <=,
84
- # ==, =~, +, -, *, /, %:
85
- #
86
- # :x == 1 #=> "(x = 1)"
87
- # (:x + 100) < 200 #=> "((x + 100) < 200)"
88
- #
89
- # The in, in?, nil and nil? method calls are intercepted and passed to
90
- # #compare_expr.
91
- #
92
- # :x.in [1, 2, 3] #=> "(x IN (1, 2, 3))"
93
- # :x.in?(DB[:y].select(:z)) #=> "(x IN (SELECT z FROM y))"
94
- # :x.nil? #=> "(x IS NULL)"
95
- #
96
- # The like and like? method calls are intercepted and passed to #match_expr.
97
- #
98
- # :x.like? 'ABC%' #=> "(x LIKE 'ABC%')"
99
- #
100
- # The method also supports SQL functions by invoking Symbol#[]:
101
- #
102
- # :avg[:x] #=> "avg(x)"
103
- # :substring[:x, 5] #=> "substring(x, 5)"
104
- #
105
- # All other method calls are evaulated as normal Ruby code.
106
- def call_expr(e, b, opts)
107
- case op = e[2]
108
- when :>, :<, :>=, :<=
109
- l = eval_expr(e[1], b, opts)
110
- r = eval_expr(e[3][1], b, opts)
111
- if l.is_one_of?(Symbol, Sequel::LiteralString, Sequel::SQL::Expression) || \
112
- r.is_one_of?(Symbol, Sequel::LiteralString, Sequel::SQL::Expression)
113
- "(#{literal(l)} #{op} #{literal(r)})"
114
- else
115
- ext_expr(e, b, opts)
116
- end
117
- when :==
118
- l = eval_expr(e[1], b, opts)
119
- r = eval_expr(e[3][1], b, opts)
120
- compare_expr(l, r)
121
- when :=~
122
- l = eval_expr(e[1], b, opts)
123
- r = eval_expr(e[3][1], b, opts)
124
- match_expr(l, r)
125
- when :+, :-, :*, :%, :/
126
- l = eval_expr(e[1], b, opts)
127
- r = eval_expr(e[3][1], b, opts)
128
- if l.is_one_of?(Symbol, Sequel::LiteralString, Sequel::SQL::Expression) || \
129
- r.is_one_of?(Symbol, Sequel::LiteralString, Sequel::SQL::Expression)
130
- "(#{literal(l)} #{op} #{literal(r)})".lit
131
- else
132
- ext_expr(e, b, opts)
133
- end
134
- when :<<
135
- l = eval_expr(e[1], b, opts)
136
- r = eval_expr(e[3][1], b, opts)
137
- "#{literal(l)} = #{literal(r)}".lit
138
- when :|
139
- l = eval_expr(e[1], b, opts)
140
- r = eval_expr(e[3][1], b, opts)
141
- if l.is_one_of?(Symbol, Sequel::SQL::Subscript)
142
- l|r
143
- else
144
- ext_expr(e, b, opts)
145
- end
146
- when :in, :in?
147
- # in/in? operators are supported using two forms:
148
- # :x.in([1, 2, 3])
149
- # :x.in(1, 2, 3) # variable arity
150
- l = eval_expr(e[1], b, opts)
151
- r = eval_expr((e[3].size == 2) ? e[3][1] : e[3], b, opts)
152
- compare_expr(l, r)
153
- when :nil, :nil?
154
- l = eval_expr(e[1], b, opts)
155
- compare_expr(l, nil)
156
- when :like, :like?
157
- l = eval_expr(e[1], b, opts)
158
- r = eval_expr(e[3][1], b, opts)
159
- match_expr(l, r)
160
- else
161
- if (op == :[]) && (e[1][0] == :lit) && (Symbol === e[1][1])
162
- # SQL Functions, e.g.: :sum[:x]
163
- if e[3]
164
- e[1][1][*eval_expr(e[3], b, opts)]
165
- else
166
- e[1][1][]
167
- end
168
- else
169
- # external code
170
- ext_expr(e, b, opts)
171
- end
172
- end
173
- end
174
-
175
- def fcall_expr(e, b, opts) #:nodoc:
176
- ext_expr(e, b, opts)
177
- end
178
-
179
- def vcall_expr(e, b, opts) #:nodoc:
180
- eval(e[1].to_s, b)
181
- end
182
-
183
- def iter_expr(e, b, opts) #:nodoc:
184
- if e[1][0] == :call && e[1][2] == :each
185
- unfold_each_expr(e, b, opts)
186
- elsif e[1] == [:fcall, :proc]
187
- eval_expr(e[3], b, opts) # inline proc
188
- else
189
- ext_expr(e, b, opts) # method call with inline proc
190
- end
191
- end
192
-
193
- def replace_dvars(a, values)
194
- a.map do |i|
195
- if i.is_a?(Array) && (i[0] == :dvar)
196
- if v = values[i[1]]
197
- value_to_parse_tree(v)
198
- else
199
- i
200
- end
201
- elsif Array === i
202
- replace_dvars(i, values)
203
- else
204
- i
205
- end
206
- end
207
- end
208
-
209
- def value_to_parse_tree(value)
210
- c = Class.new
211
- c.class_eval("def m; #{value.inspect}; end")
212
- ParseTree.translate(c, :m)[2][1][2]
213
- end
214
-
215
- def unfold_each_expr(e, b, opts) #:nodoc:
216
- source = eval_expr(e[1][1], b, opts)
217
- block_dvars = []
218
- if e[2][0] == :dasgn_curr
219
- block_dvars << e[2][1]
220
- elsif e[2][0] == :masgn
221
- e[2][1].each do |i|
222
- if i.is_a?(Array) && i[0] == :dasgn_curr
223
- block_dvars << i[1]
224
- end
225
- end
226
- end
227
- new_block = [:block]
228
-
229
- source.each do |*dvars|
230
- iter_values = (Array === dvars[0]) ? dvars[0] : dvars
231
- values = block_dvars.inject({}) {|m, i| m[i] = iter_values.shift; m}
232
- iter = replace_dvars(e[3], values)
233
- new_block << iter
234
- end
235
-
236
- pt_expr(new_block, b, opts)
237
- end
238
-
239
- # Evaluates a parse-tree into an SQL expression.
240
- def eval_expr(e, b, opts)
241
- case e[0]
242
- when :call # method call
243
- call_expr(e, b, opts)
244
- when :fcall
245
- fcall_expr(e, b, opts)
246
- when :vcall
247
- vcall_expr(e, b, opts)
248
- when :ivar, :cvar, :dvar, :const, :gvar # local ref
249
- eval(e[1].to_s, b)
250
- when :nth_ref
251
- eval("$#{e[1]}", b)
252
- when :lvar # local context
253
- if e[1] == :block
254
- pr = eval(e[1].to_s, b)
255
- "#{proc_to_sql(pr)}"
256
- else
257
- eval(e[1].to_s, b)
258
- end
259
- when :lit, :str # literal
260
- e[1]
261
- when :dot2 # inclusive range
262
- eval_expr(e[1], b, opts)..eval_expr(e[2], b, opts)
263
- when :dot3 # exclusive range
264
- eval_expr(e[1], b, opts)...eval_expr(e[2], b, opts)
265
- when :colon2 # qualified constant ref
266
- eval_expr(e[1], b, opts).const_get(e[2])
267
- when :false
268
- false
269
- when :true
270
- true
271
- when :nil
272
- nil
273
- when :array
274
- # array
275
- e[1..-1].map {|i| eval_expr(i, b, opts)}
276
- when :match3
277
- # =~/!~ operator
278
- l = eval_expr(e[2], b, opts)
279
- r = eval_expr(e[1], b, opts)
280
- compare_expr(l, r)
281
- when :iter
282
- iter_expr(e, b, opts)
283
- when :dasgn, :dasgn_curr
284
- # assignment
285
- l = e[1]
286
- r = eval_expr(e[2], b, opts)
287
- raise Sequel::Error::InvalidExpression, "#{l} = #{r}. Did you mean :#{l} == #{r}?"
288
- when :if, :dstr
289
- ext_expr(e, b, opts)
290
- else
291
- raise Sequel::Error::InvalidExpression, "Invalid expression tree: #{e.inspect}"
292
- end
293
- end
294
-
295
- JOIN_AND = " AND ".freeze
296
- JOIN_COMMA = ", ".freeze
297
-
298
- def pt_expr(e, b, opts = {}) #:nodoc:
299
- case e[0]
300
- when :not # negation: !x, (x != y), (x !~ y)
301
- if (e[1][0] == :lit) && (Symbol === e[1][1])
302
- # translate (!:x) into (x = 'f')
303
- compare_expr(e[1][1], false)
304
- else
305
- "(NOT #{pt_expr(e[1], b, opts)})"
306
- end
307
- when :and # x && y
308
- "(#{e[1..-1].map {|i| pt_expr(i, b, opts)}.join(JOIN_AND)})"
309
- when :or # x || y
310
- "(#{pt_expr(e[1], b, opts)} OR #{pt_expr(e[2], b, opts)})"
311
- when :call, :vcall, :iter, :match3 # method calls, blocks
312
- eval_expr(e, b, opts)
313
- when :block # block of statements
314
- if opts[:comma_separated]
315
- "#{e[1..-1].map {|i| pt_expr(i, b, opts)}.join(JOIN_COMMA)}"
316
- else
317
- "(#{e[1..-1].map {|i| pt_expr(i, b, opts)}.join(JOIN_AND)})"
318
- end
319
- else # literals
320
- if e == [:lvar, :block]
321
- eval_expr(e, b, opts)
322
- else
323
- literal(eval_expr(e, b, opts))
324
- end
325
- end
326
- end
327
-
328
- # Translates a Ruby block into an SQL expression.
329
- def proc_to_sql(proc, opts = {})
330
- c = Class.new {define_method(:m, &proc)}
331
- pt_expr(ParseTree.translate(c, :m)[2][2], proc.binding, opts)
332
- end
333
- end
334
- end
335
-
336
- begin
337
- require 'parse_tree'
338
- rescue Exception
339
- module Sequel::Dataset::Sequelizer
340
- def proc_to_sql(proc)
341
- raise Sequel::Error, "You must have the ParseTree gem installed in order to use block filters."
342
- end
343
- end
344
- end
345
-
346
- begin
347
- require 'ruby2ruby'
348
- rescue Exception
349
- module Sequel::Dataset::Sequelizer
350
- def ext_expr(e)
351
- raise Sequel::Error, "You must have the Ruby2Ruby gem installed in order to use this block filter."
352
- end
353
- end
354
- end
@@ -1,586 +0,0 @@
1
- module Sequel
2
- class Dataset
3
- # The Dataset SQL module implements all the dataset methods concerned with
4
- # generating SQL statements for retrieving and manipulating records.
5
- module SQL
6
- # Adds quoting to column references. This method is just a stub and can
7
- # be overriden in adapters in order to provide correct column quoting
8
- # behavior.
9
- def quote_column_ref(name); name.to_s; end
10
-
11
- ALIASED_REGEXP = /^(.*)\s(.*)$/.freeze
12
- QUALIFIED_REGEXP = /^(.*)\.(.*)$/.freeze
13
-
14
- # Returns a qualified column name (including a table name) if the column
15
- # name isn't already qualified.
16
- def qualified_column_name(column, table)
17
- s = literal(column)
18
- if s =~ QUALIFIED_REGEXP
19
- return column
20
- else
21
- if (table =~ ALIASED_REGEXP)
22
- table = $2
23
- end
24
- Sequel::SQL::QualifiedColumnRef.new(table, column)
25
- # "#{table}.#{column}"
26
- end
27
- end
28
-
29
- WILDCARD = '*'.freeze
30
- COMMA_SEPARATOR = ", ".freeze
31
-
32
- # Converts an array of column names into a comma seperated string of
33
- # column names. If the array is empty, a wildcard (*) is returned.
34
- def column_list(columns)
35
- if columns.empty?
36
- WILDCARD
37
- else
38
- m = columns.map do |i|
39
- i.is_a?(Hash) ? i.map {|kv| "#{literal(kv[0])} AS #{kv[1]}"} : literal(i)
40
- end
41
- m.join(COMMA_SEPARATOR)
42
- end
43
- end
44
-
45
- # Converts an array of sources names into into a comma separated list.
46
- def source_list(source)
47
- if source.nil? || source.empty?
48
- raise Error, 'No source specified for query'
49
- end
50
- auto_alias_count = 0
51
- m = source.map do |i|
52
- case i
53
- when Dataset
54
- auto_alias_count += 1
55
- i.to_table_reference(auto_alias_count)
56
- when Hash
57
- i.map {|k, v| "#{k.is_a?(Dataset) ? k.to_table_reference : k} #{v}"}.
58
- join(COMMA_SEPARATOR)
59
- else
60
- i
61
- end
62
- end
63
- m.join(COMMA_SEPARATOR)
64
- end
65
-
66
- NULL = "NULL".freeze
67
- TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S'".freeze
68
- DATE_FORMAT = "DATE '%Y-%m-%d'".freeze
69
- TRUE = "'t'".freeze
70
- FALSE = "'f'".freeze
71
-
72
- # Returns a literal representation of a value to be used as part
73
- # of an SQL expression. The stock implementation supports literalization
74
- # of String (with proper escaping to prevent SQL injections), numbers,
75
- # Symbol (as column references), Array (as a list of literalized values),
76
- # Time (as an SQL TIMESTAMP), Date (as an SQL DATE), Dataset (as a
77
- # subquery) and nil (AS NULL).
78
- #
79
- # dataset.literal("abc'def") #=> "'abc''def'"
80
- # dataset.literal(:items__id) #=> "items.id"
81
- # dataset.literal([1, 2, 3]) => "(1, 2, 3)"
82
- # dataset.literal(DB[:items]) => "(SELECT * FROM items)"
83
- #
84
- # If an unsupported object is given, an exception is raised.
85
- def literal(v)
86
- case v
87
- when LiteralString
88
- v
89
- when String
90
- "'#{v.gsub(/'/, "''")}'"
91
- when Integer, Float
92
- v.to_s
93
- when BigDecimal
94
- v.to_s("F")
95
- when NilClass
96
- NULL
97
- when TrueClass
98
- TRUE
99
- when FalseClass
100
- FALSE
101
- when Symbol
102
- v.to_column_ref(self)
103
- when Sequel::SQL::Expression
104
- v.to_s(self)
105
- when Array
106
- v.empty? ? NULL : v.map {|i| literal(i)}.join(COMMA_SEPARATOR)
107
- when Time
108
- v.strftime(TIMESTAMP_FORMAT)
109
- when Date
110
- v.strftime(DATE_FORMAT)
111
- when Dataset
112
- "(#{v.sql})"
113
- else
114
- raise Error, "can't express #{v.inspect} as a SQL literal"
115
- end
116
- end
117
-
118
- AND_SEPARATOR = " AND ".freeze
119
- QUESTION_MARK = '?'.freeze
120
-
121
- # Formats a where clause. If parenthesize is true, then the whole
122
- # generated clause will be enclosed in a set of parentheses.
123
- def expression_list(expr, parenthesize = false)
124
- case expr
125
- when Hash
126
- parenthesize = false if expr.size == 1
127
- fmt = expr.map {|i| compare_expr(i[0], i[1])}.join(AND_SEPARATOR)
128
- when Array
129
- fmt = expr.shift.gsub(QUESTION_MARK) {literal(expr.shift)}
130
- when Proc
131
- fmt = proc_to_sql(expr)
132
- else
133
- # if the expression is compound, it should be parenthesized in order for
134
- # things to be predictable (when using #or and #and.)
135
- parenthesize |= expr =~ /\).+\(/
136
- fmt = expr
137
- end
138
- parenthesize ? "(#{fmt})" : fmt
139
- end
140
-
141
- # Returns a copy of the dataset with the source changed.
142
- def from(*source)
143
- clone_merge(:from => source)
144
- end
145
-
146
- # Returns a copy of the dataset with the selected columns changed.
147
- def select(*columns)
148
- clone_merge(:select => columns)
149
- end
150
-
151
- # Returns a copy of the dataset with the distinct option.
152
- def uniq(*args)
153
- clone_merge(:distinct => args)
154
- end
155
- alias_method :distinct, :uniq
156
-
157
- # Returns a copy of the dataset with the order changed.
158
- def order(*order)
159
- clone_merge(:order => order)
160
- end
161
-
162
- alias_method :order_by, :order
163
-
164
- # Returns a copy of the dataset with the order reversed. If no order is
165
- # given, the existing order is inverted.
166
- def reverse_order(*order)
167
- order(*invert_order(order.empty? ? @opts[:order] : order))
168
- end
169
-
170
- # Inverts the given order by breaking it into a list of column references
171
- # and inverting them.
172
- #
173
- # dataset.invert_order([:id.desc]]) #=> [:id]
174
- # dataset.invert_order(:category, :price.desc]) #=>
175
- # [:category.desc, :price]
176
- def invert_order(order)
177
- new_order = []
178
- order.map do |f|
179
- if f.is_a?(Sequel::SQL::ColumnExpr) && (f.op == Sequel::SQL::ColumnMethods::DESC)
180
- f.l
181
- else
182
- f.desc
183
- end
184
- end
185
- end
186
-
187
- # Returns a copy of the dataset with the results grouped by the value of
188
- # the given columns
189
- def group(*columns)
190
- clone_merge(:group => columns)
191
- end
192
-
193
- alias_method :group_by, :group
194
-
195
- # Returns a copy of the dataset with the given conditions imposed upon it.
196
- # If the query has been grouped, then the conditions are imposed in the
197
- # HAVING clause. If not, then they are imposed in the WHERE clause. Filter
198
- # accepts a Hash (formated into a list of equality expressions), an Array
199
- # (formatted ala ActiveRecord conditions), a String (taken literally), or
200
- # a block that is converted into expressions.
201
- #
202
- # dataset.filter(:id => 3).sql #=>
203
- # "SELECT * FROM items WHERE (id = 3)"
204
- # dataset.filter('price < ?', 100).sql #=>
205
- # "SELECT * FROM items WHERE price < 100"
206
- # dataset.filter('price < 100').sql #=>
207
- # "SELECT * FROM items WHERE price < 100"
208
- # dataset.filter {price < 100}.sql #=>
209
- # "SELECT * FROM items WHERE (price < 100)"
210
- #
211
- # Multiple filter calls can be chained for scoping:
212
- #
213
- # software = dataset.filter(:category => 'software')
214
- # software.filter {price < 100}.sql #=>
215
- # "SELECT * FROM items WHERE (category = 'software') AND (price < 100)"
216
- def filter(*cond, &block)
217
- clause = (@opts[:group] ? :having : :where)
218
- cond = cond.first if cond.size == 1
219
- if cond === true || cond === false
220
- raise Error::InvalidFilter, "Invalid filter specified. Did you mean to supply a block?"
221
- end
222
- parenthesize = !(cond.is_a?(Hash) || cond.is_a?(Array))
223
- filter = cond.is_a?(Hash) && cond
224
- if @opts[clause]
225
- l = expression_list(@opts[clause])
226
- r = expression_list(block || cond, parenthesize)
227
- clone_merge(clause => "#{l} AND #{r}")
228
- else
229
- clone_merge(:filter => cond, clause => expression_list(block || cond))
230
- end
231
- end
232
-
233
- # Adds an alternate filter to an existing filter using OR. If no filter
234
- # exists an error is raised.
235
- def or(*cond, &block)
236
- clause = (@opts[:group] ? :having : :where)
237
- cond = cond.first if cond.size == 1
238
- parenthesize = !(cond.is_a?(Hash) || cond.is_a?(Array))
239
- if @opts[clause]
240
- l = expression_list(@opts[clause])
241
- r = expression_list(block || cond, parenthesize)
242
- clone_merge(clause => "#{l} OR #{r}")
243
- else
244
- raise Error::NoExistingFilter, "No existing filter found."
245
- end
246
- end
247
-
248
- # Adds an further filter to an existing filter using AND. If no filter
249
- # exists an error is raised. This method is identical to #filter except
250
- # it expects an existing filter.
251
- def and(*cond, &block)
252
- clause = (@opts[:group] ? :having : :where)
253
- unless @opts[clause]
254
- raise Error::NoExistingFilter, "No existing filter found."
255
- end
256
- filter(*cond, &block)
257
- end
258
-
259
- # Performs the inverse of Dataset#filter.
260
- #
261
- # dataset.exclude(:category => 'software').sql #=>
262
- # "SELECT * FROM items WHERE NOT (category = 'software')"
263
- def exclude(*cond, &block)
264
- clause = (@opts[:group] ? :having : :where)
265
- cond = cond.first if cond.size == 1
266
- parenthesize = !(cond.is_a?(Hash) || cond.is_a?(Array))
267
- if @opts[clause]
268
- l = expression_list(@opts[clause])
269
- r = expression_list(block || cond, parenthesize)
270
- cond = "#{l} AND (NOT #{r})"
271
- else
272
- cond = "(NOT #{expression_list(block || cond, true)})"
273
- end
274
- clone_merge(clause => cond)
275
- end
276
-
277
- # Returns a copy of the dataset with the where conditions changed. Raises
278
- # if the dataset has been grouped. See also #filter.
279
- def where(*cond, &block)
280
- if @opts[:group]
281
- raise Error, "Can't specify a WHERE clause once the dataset has been grouped"
282
- else
283
- filter(*cond, &block)
284
- end
285
- end
286
-
287
- # Returns a copy of the dataset with the having conditions changed. Raises
288
- # if the dataset has not been grouped. See also #filter
289
- def having(*cond, &block)
290
- unless @opts[:group]
291
- raise Error, "Can only specify a HAVING clause on a grouped dataset"
292
- else
293
- filter(*cond, &block)
294
- end
295
- end
296
-
297
- # Adds a UNION clause using a second dataset object. If all is true the
298
- # clause used is UNION ALL, which may return duplicate rows.
299
- def union(dataset, all = false)
300
- clone_merge(:union => dataset, :union_all => all)
301
- end
302
-
303
- # Adds an INTERSECT clause using a second dataset object. If all is true
304
- # the clause used is INTERSECT ALL, which may return duplicate rows.
305
- def intersect(dataset, all = false)
306
- clone_merge(:intersect => dataset, :intersect_all => all)
307
- end
308
-
309
- # Adds an EXCEPT clause using a second dataset object. If all is true the
310
- # clause used is EXCEPT ALL, which may return duplicate rows.
311
- def except(dataset, all = false)
312
- clone_merge(:except => dataset, :except_all => all)
313
- end
314
-
315
- JOIN_TYPES = {
316
- :left_outer => 'LEFT OUTER JOIN'.freeze,
317
- :right_outer => 'RIGHT OUTER JOIN'.freeze,
318
- :full_outer => 'FULL OUTER JOIN'.freeze,
319
- :inner => 'INNER JOIN'.freeze
320
- }
321
-
322
- # Returns a join clause based on the specified join type and condition.
323
- def join_expr(type, table, expr)
324
- join_type = JOIN_TYPES[type || :inner]
325
- unless join_type
326
- raise Error::InvalidJoinType, "Invalid join type: #{type}"
327
- end
328
-
329
- join_conditions = {}
330
- expr.each do |k, v|
331
- k = qualified_column_name(k, table) if k.is_a?(Symbol)
332
- v = qualified_column_name(v, @opts[:last_joined_table] || @opts[:from].first) if v.is_a?(Symbol)
333
- join_conditions[k] = v
334
- end
335
- " #{join_type} #{table} ON #{expression_list(join_conditions)}"
336
- end
337
-
338
- # Returns a joined dataset with the specified join type and condition.
339
- def join_table(type, table, expr)
340
- unless expr.is_a?(Hash)
341
- expr = {expr => :id}
342
- end
343
- clause = join_expr(type, table, expr)
344
- join = @opts[:join] ? @opts[:join] + clause : clause
345
- clone_merge(:join => join, :last_joined_table => table)
346
- end
347
-
348
- # Returns a LEFT OUTER joined dataset.
349
- def left_outer_join(table, expr); join_table(:left_outer, table, expr); end
350
-
351
- # Returns a RIGHT OUTER joined dataset.
352
- def right_outer_join(table, expr); join_table(:right_outer, table, expr); end
353
-
354
- # Returns an OUTER joined dataset.
355
- def full_outer_join(table, expr); join_table(:full_outer, table, expr); end
356
-
357
- # Returns an INNER joined dataset.
358
- def inner_join(table, expr); join_table(:inner, table, expr); end
359
- alias join inner_join
360
-
361
- # Inserts multiple values. If a block is given it is invoked for each
362
- # item in the given array before inserting it.
363
- def insert_multiple(array, &block)
364
- if block
365
- array.each {|i| insert(block[i])}
366
- else
367
- array.each {|i| insert(i)}
368
- end
369
- end
370
-
371
- # Formats a SELECT statement using the given options and the dataset
372
- # options.
373
- def select_sql(opts = nil)
374
- opts = opts ? @opts.merge(opts) : @opts
375
-
376
- if sql = opts[:sql]
377
- return sql
378
- end
379
-
380
- columns = opts[:select]
381
- select_columns = columns ? column_list(columns) : WILDCARD
382
-
383
- if distinct = opts[:distinct]
384
- distinct_clause = distinct.empty? ? "DISTINCT" : "DISTINCT ON (#{column_list(distinct)})"
385
- sql = "SELECT #{distinct_clause} #{select_columns}"
386
- else
387
- sql = "SELECT #{select_columns}"
388
- end
389
-
390
- if opts[:from]
391
- sql << " FROM #{source_list(opts[:from])}"
392
- end
393
-
394
- if join = opts[:join]
395
- sql << join
396
- end
397
-
398
- if where = opts[:where]
399
- sql << " WHERE #{where}"
400
- end
401
-
402
- if group = opts[:group]
403
- sql << " GROUP BY #{column_list(group)}"
404
- end
405
-
406
- if order = opts[:order]
407
- sql << " ORDER BY #{column_list(order)}"
408
- end
409
-
410
- if having = opts[:having]
411
- sql << " HAVING #{having}"
412
- end
413
-
414
- if limit = opts[:limit]
415
- sql << " LIMIT #{limit}"
416
- if offset = opts[:offset]
417
- sql << " OFFSET #{offset}"
418
- end
419
- end
420
-
421
- if union = opts[:union]
422
- sql << (opts[:union_all] ? \
423
- " UNION ALL #{union.sql}" : " UNION #{union.sql}")
424
- elsif intersect = opts[:intersect]
425
- sql << (opts[:intersect_all] ? \
426
- " INTERSECT ALL #{intersect.sql}" : " INTERSECT #{intersect.sql}")
427
- elsif except = opts[:except]
428
- sql << (opts[:except_all] ? \
429
- " EXCEPT ALL #{except.sql}" : " EXCEPT #{except.sql}")
430
- end
431
-
432
- sql
433
- end
434
- alias_method :sql, :select_sql
435
-
436
- # Formats an INSERT statement using the given values. If a hash is given,
437
- # the resulting statement includes column names. If no values are given,
438
- # the resulting statement includes a DEFAULT VALUES clause.
439
- #
440
- # dataset.insert_sql() #=> 'INSERT INTO items DEFAULT VALUES'
441
- # dataset.insert_sql(1,2,3) #=> 'INSERT INTO items VALUES (1, 2, 3)'
442
- # dataset.insert_sql(:a => 1, :b => 2) #=>
443
- # 'INSERT INTO items (a, b) VALUES (1, 2)'
444
- def insert_sql(*values)
445
- if values.empty?
446
- "INSERT INTO #{@opts[:from]} DEFAULT VALUES"
447
- else
448
- values = values[0] if values.size == 1
449
- case values
450
- when Sequel::Model
451
- insert_sql(values.values)
452
- when Array
453
- if values.empty?
454
- "INSERT INTO #{@opts[:from]} DEFAULT VALUES"
455
- elsif values.keys
456
- fl = values.keys.map {|f| literal(f.to_sym)}
457
- vl = @transform ? transform_save(values.values) : values.values
458
- vl.map! {|v| literal(v)}
459
- "INSERT INTO #{@opts[:from]} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
460
- else
461
- "INSERT INTO #{@opts[:from]} VALUES (#{literal(values)})"
462
- end
463
- when Hash
464
- values = transform_save(values) if @transform
465
- if values.empty?
466
- "INSERT INTO #{@opts[:from]} DEFAULT VALUES"
467
- else
468
- fl, vl = [], []
469
- values.each {|k, v| fl << literal(k.to_sym); vl << literal(v)}
470
- "INSERT INTO #{@opts[:from]} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
471
- end
472
- when Dataset
473
- "INSERT INTO #{@opts[:from]} #{literal(values)}"
474
- else
475
- "INSERT INTO #{@opts[:from]} VALUES (#{literal(values)})"
476
- end
477
- end
478
- end
479
-
480
- # Formats an UPDATE statement using the given values.
481
- #
482
- # dataset.update_sql(:price => 100, :category => 'software') #=>
483
- # "UPDATE items SET price = 100, category = 'software'"
484
- def update_sql(values = {}, opts = nil, &block)
485
- opts = opts ? @opts.merge(opts) : @opts
486
-
487
- if opts[:group]
488
- raise Error::InvalidOperation, "A grouped dataset cannot be updated"
489
- elsif (opts[:from].size > 1) or opts[:join]
490
- raise Error::InvalidOperation, "A joined dataset cannot be updated"
491
- end
492
-
493
- sql = "UPDATE #{@opts[:from]} SET "
494
- if block
495
- sql << proc_to_sql(block, :comma_separated => true)
496
- else
497
- # check if array with keys
498
- values = values.to_hash if values.is_a?(Array) && values.keys
499
- if values.is_a?(Hash)
500
- # get values from hash
501
- values = transform_save(values) if @transform
502
- set = values.map do |k, v|
503
- # convert string key into symbol
504
- k = k.to_sym if String === k
505
- "#{literal(k)} = #{literal(v)}"
506
- end.join(COMMA_SEPARATOR)
507
- else
508
- # copy values verbatim
509
- set = values
510
- end
511
- sql << set
512
- end
513
- if where = opts[:where]
514
- sql << " WHERE #{where}"
515
- end
516
-
517
- sql
518
- end
519
-
520
- # Formats a DELETE statement using the given options and dataset options.
521
- #
522
- # dataset.filter {price >= 100}.delete_sql #=>
523
- # "DELETE FROM items WHERE (price >= 100)"
524
- def delete_sql(opts = nil)
525
- opts = opts ? @opts.merge(opts) : @opts
526
-
527
- if opts[:group]
528
- raise Error::InvalidOperation, "Grouped datasets cannot be deleted from"
529
- elsif opts[:from].is_a?(Array) && opts[:from].size > 1
530
- raise Error::InvalidOperation, "Joined datasets cannot be deleted from"
531
- end
532
-
533
- sql = "DELETE FROM #{opts[:from]}"
534
-
535
- if where = opts[:where]
536
- sql << " WHERE #{where}"
537
- end
538
-
539
- sql
540
- end
541
-
542
- # Returns a table reference for use in the FROM clause. If the dataset has
543
- # only a :from option refering to a single table, only the table name is
544
- # returned. Otherwise a subquery is returned.
545
- def to_table_reference(idx = nil)
546
- if opts.keys == [:from] && opts[:from].size == 1
547
- opts[:from].first.to_s
548
- else
549
- idx ? "(#{sql}) t#{idx}" : "(#{sql})"
550
- end
551
- end
552
-
553
- # Returns an EXISTS clause for the dataset.
554
- #
555
- # dataset.exists #=> "EXISTS (SELECT 1 FROM items)"
556
- def exists(opts = nil)
557
- "EXISTS (#{sql({:select => [1]}.merge(opts || {}))})"
558
- end
559
-
560
- # If given an integer, the dataset will contain only the first l results.
561
- # If given a range, it will contain only those at offsets within that
562
- # range. If a second argument is given, it is used as an offset.
563
- def limit(l, o = nil)
564
- if l.is_a? Range
565
- lim = (l.exclude_end? ? l.last - l.first : l.last + 1 - l.first)
566
- clone_merge(:limit => lim, :offset=>l.first)
567
- elsif o
568
- clone_merge(:limit => l, :offset => o)
569
- else
570
- clone_merge(:limit => l)
571
- end
572
- end
573
-
574
- STOCK_COUNT_OPTS = {:select => ["COUNT(*)".lit], :order => nil}.freeze
575
-
576
- # Returns the number of records in the dataset.
577
- def count
578
- opts = @opts[:sql] ? \
579
- {:sql => "SELECT COUNT(*) FROM (#{@opts[:sql]}) AS c", :order => nil} : \
580
- STOCK_COUNT_OPTS
581
-
582
- single_value(opts).to_i
583
- end
584
- end
585
- end
586
- end