sequel 3.5.0 → 3.6.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 (72) hide show
  1. data/CHANGELOG +108 -0
  2. data/README.rdoc +25 -14
  3. data/Rakefile +20 -1
  4. data/doc/advanced_associations.rdoc +61 -64
  5. data/doc/cheat_sheet.rdoc +16 -7
  6. data/doc/opening_databases.rdoc +3 -3
  7. data/doc/prepared_statements.rdoc +1 -1
  8. data/doc/reflection.rdoc +2 -1
  9. data/doc/release_notes/3.6.0.txt +366 -0
  10. data/doc/schema.rdoc +19 -14
  11. data/lib/sequel/adapters/amalgalite.rb +5 -27
  12. data/lib/sequel/adapters/jdbc.rb +13 -3
  13. data/lib/sequel/adapters/jdbc/h2.rb +17 -0
  14. data/lib/sequel/adapters/jdbc/mysql.rb +20 -7
  15. data/lib/sequel/adapters/mysql.rb +4 -3
  16. data/lib/sequel/adapters/oracle.rb +1 -1
  17. data/lib/sequel/adapters/postgres.rb +87 -28
  18. data/lib/sequel/adapters/shared/mssql.rb +47 -6
  19. data/lib/sequel/adapters/shared/mysql.rb +12 -31
  20. data/lib/sequel/adapters/shared/postgres.rb +15 -12
  21. data/lib/sequel/adapters/shared/sqlite.rb +18 -0
  22. data/lib/sequel/adapters/sqlite.rb +1 -16
  23. data/lib/sequel/connection_pool.rb +1 -1
  24. data/lib/sequel/core.rb +1 -1
  25. data/lib/sequel/database.rb +1 -1
  26. data/lib/sequel/database/schema_generator.rb +2 -0
  27. data/lib/sequel/database/schema_sql.rb +1 -1
  28. data/lib/sequel/dataset.rb +5 -179
  29. data/lib/sequel/dataset/actions.rb +123 -0
  30. data/lib/sequel/dataset/convenience.rb +18 -10
  31. data/lib/sequel/dataset/features.rb +65 -0
  32. data/lib/sequel/dataset/prepared_statements.rb +29 -23
  33. data/lib/sequel/dataset/query.rb +429 -0
  34. data/lib/sequel/dataset/sql.rb +67 -435
  35. data/lib/sequel/model/associations.rb +77 -13
  36. data/lib/sequel/model/base.rb +30 -8
  37. data/lib/sequel/model/errors.rb +4 -4
  38. data/lib/sequel/plugins/caching.rb +38 -15
  39. data/lib/sequel/plugins/force_encoding.rb +18 -7
  40. data/lib/sequel/plugins/hook_class_methods.rb +4 -0
  41. data/lib/sequel/plugins/many_through_many.rb +1 -1
  42. data/lib/sequel/plugins/nested_attributes.rb +40 -11
  43. data/lib/sequel/plugins/serialization.rb +17 -3
  44. data/lib/sequel/plugins/validation_helpers.rb +65 -18
  45. data/lib/sequel/sql.rb +23 -1
  46. data/lib/sequel/version.rb +1 -1
  47. data/spec/adapters/mssql_spec.rb +96 -10
  48. data/spec/adapters/mysql_spec.rb +19 -0
  49. data/spec/adapters/postgres_spec.rb +65 -2
  50. data/spec/adapters/sqlite_spec.rb +10 -0
  51. data/spec/core/core_sql_spec.rb +9 -0
  52. data/spec/core/database_spec.rb +8 -4
  53. data/spec/core/dataset_spec.rb +122 -29
  54. data/spec/core/expression_filters_spec.rb +17 -0
  55. data/spec/extensions/caching_spec.rb +43 -3
  56. data/spec/extensions/force_encoding_spec.rb +43 -1
  57. data/spec/extensions/nested_attributes_spec.rb +55 -2
  58. data/spec/extensions/validation_helpers_spec.rb +71 -0
  59. data/spec/integration/associations_test.rb +281 -0
  60. data/spec/integration/dataset_test.rb +383 -9
  61. data/spec/integration/eager_loader_test.rb +0 -65
  62. data/spec/integration/model_test.rb +110 -0
  63. data/spec/integration/plugin_test.rb +306 -0
  64. data/spec/integration/prepared_statement_test.rb +32 -0
  65. data/spec/integration/schema_test.rb +8 -3
  66. data/spec/integration/spec_helper.rb +1 -25
  67. data/spec/model/association_reflection_spec.rb +38 -0
  68. data/spec/model/associations_spec.rb +184 -8
  69. data/spec/model/eager_loading_spec.rb +23 -0
  70. data/spec/model/model_spec.rb +8 -0
  71. data/spec/model/record_spec.rb +84 -1
  72. metadata +9 -2
@@ -7,6 +7,17 @@ module Sequel
7
7
  clauses.map{|clause| :"#{type}_#{clause}_sql"}.freeze
8
8
  end
9
9
 
10
+ # These symbols have _join methods created (e.g. inner_join) that
11
+ # call join_table with the symbol, passing along the arguments and
12
+ # block from the method call.
13
+ CONDITIONED_JOIN_TYPES = [:inner, :full_outer, :right_outer, :left_outer, :full, :right, :left]
14
+
15
+ # These symbols have _join methods created (e.g. natural_join) that
16
+ # call join_table with the symbol. They only accept a single table
17
+ # argument which is passed to join_table, and they raise an error
18
+ # if called with a block.
19
+ UNCONDITIONED_JOIN_TYPES = [:natural, :natural_left, :natural_right, :natural_full, :cross]
20
+
10
21
  AND_SEPARATOR = " AND ".freeze
11
22
  BOOL_FALSE = "'f'".freeze
12
23
  BOOL_TRUE = "'t'".freeze
@@ -15,14 +26,12 @@ module Sequel
15
26
  COLUMN_REF_RE3 = /\A([\w ]+)__([\w ]+)\z/.freeze
16
27
  COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit, :compounds]
17
28
  DATASET_ALIAS_BASE_NAME = 't'.freeze
18
- FROM_SELF_KEEP_OPTS = [:graph, :eager_graph, :graph_aliases]
19
29
  IS_LITERALS = {nil=>'NULL'.freeze, true=>'TRUE'.freeze, false=>'FALSE'.freeze}.freeze
20
30
  IS_OPERATORS = ::Sequel::SQL::ComplexExpression::IS_OPERATORS
21
31
  N_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::N_ARITY_OPERATORS
22
32
  NULL = "NULL".freeze
23
33
  QUALIFY_KEYS = [:select, :where, :having, :order, :group]
24
34
  QUESTION_MARK = '?'.freeze
25
- STOCK_COUNT_OPTS = {:select => [SQL::AliasedExpression.new(LiteralString.new("COUNT(*)").freeze, :count)], :order => nil}.freeze
26
35
  DELETE_CLAUSE_METHODS = clause_methods(:delete, %w'from where')
27
36
  INSERT_CLAUSE_METHODS = clause_methods(:insert, %w'into columns values')
28
37
  SELECT_CLAUSE_METHODS = clause_methods(:select, %w'with distinct columns from join where group having compounds order limit')
@@ -33,16 +42,6 @@ module Sequel
33
42
  WILDCARD = '*'.freeze
34
43
  SQL_WITH = "WITH ".freeze
35
44
 
36
- # Adds an further filter to an existing filter using AND. If no filter
37
- # exists an error is raised. This method is identical to #filter except
38
- # it expects an existing filter.
39
- #
40
- # ds.filter(:a).and(:b) # SQL: WHERE a AND b
41
- def and(*cond, &block)
42
- raise(InvalidOperation, "No existing filter found.") unless @opts[:having] || @opts[:where]
43
- filter(*cond, &block)
44
- end
45
-
46
45
  # SQL fragment for the aliased expression
47
46
  def aliased_expression_sql(ae)
48
47
  as_sql(literal(ae.expression), ae.aliaz)
@@ -86,6 +85,14 @@ module Sequel
86
85
  else
87
86
  complex_expression_sql(:OR, [SQL::BooleanExpression.new(:"!=", *args), SQL::BooleanExpression.new(:IS, args.at(0), nil)])
88
87
  end
88
+ when :IN, :"NOT IN"
89
+ cols = args.at(0)
90
+ if !supports_multiple_column_in? && cols.is_a?(Array)
91
+ expr = SQL::BooleanExpression.new(:OR, *args.at(1).to_a.map{|vals| SQL::BooleanExpression.from_value_pairs(cols.zip(vals).map{|col, val| [col, val]})})
92
+ literal(op == :IN ? expr : ~expr)
93
+ else
94
+ "(#{literal(cols)} #{op} #{literal(args.at(1))})"
95
+ end
89
96
  when *TWO_ARITY_OPERATORS
90
97
  "(#{literal(args.at(0))} #{op} #{literal(args.at(1))})"
91
98
  when *N_ARITY_OPERATORS
@@ -108,7 +115,7 @@ module Sequel
108
115
 
109
116
  # Returns the number of records in the dataset.
110
117
  def count
111
- options_overlap(COUNT_FROM_SELF_OPTS) ? from_self.count : clone(STOCK_COUNT_OPTS).single_value.to_i
118
+ aggregate_dataset.get{COUNT(:*){}.as(count)}.to_i
112
119
  end
113
120
 
114
121
  # Formats a DELETE statement using the given options and dataset options.
@@ -121,50 +128,6 @@ module Sequel
121
128
  clause_sql(:delete)
122
129
  end
123
130
 
124
- # Returns a copy of the dataset with the SQL DISTINCT clause.
125
- # The DISTINCT clause is used to remove duplicate rows from the
126
- # output. If arguments are provided, uses a DISTINCT ON clause,
127
- # in which case it will only be distinct on those columns, instead
128
- # of all returned columns. Raises an error if arguments
129
- # are given and DISTINCT ON is not supported.
130
- #
131
- # dataset.distinct # SQL: SELECT DISTINCT * FROM items
132
- # dataset.order(:id).distinct(:id) # SQL: SELECT DISTINCT ON (id) * FROM items ORDER BY id
133
- def distinct(*args)
134
- raise(InvalidOperation, "DISTINCT ON not supported") if !args.empty? && !supports_distinct_on?
135
- clone(:distinct => args)
136
- end
137
-
138
- # Adds an EXCEPT clause using a second dataset object.
139
- # An EXCEPT compound dataset returns all rows in the current dataset
140
- # that are not in the given dataset.
141
- # Raises an InvalidOperation if the operation is not supported.
142
- # Options:
143
- # * :all - Set to true to use EXCEPT ALL instead of EXCEPT, so duplicate rows can occur
144
- # * :from_self - Set to false to not wrap the returned dataset in a from_self, use with care.
145
- #
146
- # DB[:items].except(DB[:other_items]).sql
147
- # #=> "SELECT * FROM items EXCEPT SELECT * FROM other_items"
148
- def except(dataset, opts={})
149
- opts = {:all=>opts} unless opts.is_a?(Hash)
150
- raise(InvalidOperation, "EXCEPT not supported") unless supports_intersect_except?
151
- raise(InvalidOperation, "EXCEPT ALL not supported") if opts[:all] && !supports_intersect_except_all?
152
- compound_clone(:except, dataset, opts)
153
- end
154
-
155
- # Performs the inverse of Dataset#filter.
156
- #
157
- # dataset.exclude(:category => 'software').sql #=>
158
- # "SELECT * FROM items WHERE (category != 'software')"
159
- def exclude(*cond, &block)
160
- clause = (@opts[:having] ? :having : :where)
161
- cond = cond.first if cond.size == 1
162
- cond = filter_expr(cond, &block)
163
- cond = SQL::BooleanExpression.invert(cond)
164
- cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
165
- clone(clause => cond)
166
- end
167
-
168
131
  # Returns an EXISTS clause for the dataset as a LiteralString.
169
132
  #
170
133
  # DB.select(1).where(DB[:items].exists).sql
@@ -173,57 +136,6 @@ module Sequel
173
136
  LiteralString.new("EXISTS (#{select_sql})")
174
137
  end
175
138
 
176
- # Returns a copy of the dataset with the given conditions imposed upon it.
177
- # If the query already has a HAVING clause, then the conditions are imposed in the
178
- # HAVING clause. If not, then they are imposed in the WHERE clause.
179
- #
180
- # filter accepts the following argument types:
181
- #
182
- # * Hash - list of equality/inclusion expressions
183
- # * Array - depends:
184
- # * If first member is a string, assumes the rest of the arguments
185
- # are parameters and interpolates them into the string.
186
- # * If all members are arrays of length two, treats the same way
187
- # as a hash, except it allows for duplicate keys to be
188
- # specified.
189
- # * String - taken literally
190
- # * Symbol - taken as a boolean column argument (e.g. WHERE active)
191
- # * Sequel::SQL::BooleanExpression - an existing condition expression,
192
- # probably created using the Sequel expression filter DSL.
193
- #
194
- # filter also takes a block, which should return one of the above argument
195
- # types, and is treated the same way. This block yields a virtual row object,
196
- # which is easy to use to create identifiers and functions.
197
- #
198
- # If both a block and regular argument
199
- # are provided, they get ANDed together.
200
- #
201
- # Examples:
202
- #
203
- # dataset.filter(:id => 3).sql #=>
204
- # "SELECT * FROM items WHERE (id = 3)"
205
- # dataset.filter('price < ?', 100).sql #=>
206
- # "SELECT * FROM items WHERE price < 100"
207
- # dataset.filter([[:id, (1,2,3)], [:id, 0..10]]).sql #=>
208
- # "SELECT * FROM items WHERE ((id IN (1, 2, 3)) AND ((id >= 0) AND (id <= 10)))"
209
- # dataset.filter('price < 100').sql #=>
210
- # "SELECT * FROM items WHERE price < 100"
211
- # dataset.filter(:active).sql #=>
212
- # "SELECT * FROM items WHERE :active
213
- # dataset.filter{|o| o.price < 100}.sql #=>
214
- # "SELECT * FROM items WHERE (price < 100)"
215
- #
216
- # Multiple filter calls can be chained for scoping:
217
- #
218
- # software = dataset.filter(:category => 'software')
219
- # software.filter{|o| o.price < 100}.sql #=>
220
- # "SELECT * FROM items WHERE ((category = 'software') AND (price < 100))"
221
- #
222
- # See doc/dataset_filters.rdoc for more examples and details.
223
- def filter(*cond, &block)
224
- _filter(@opts[:having] ? :having : :where, *cond, &block)
225
- end
226
-
227
139
  # The first source (primary table) for this dataset. If the dataset doesn't
228
140
  # have a table, raises an error. If the table is aliased, returns the aliased name.
229
141
  def first_source_alias
@@ -243,87 +155,12 @@ module Sequel
243
155
  end
244
156
  alias first_source first_source_alias
245
157
 
246
- # Returns a copy of the dataset with the source changed.
247
- #
248
- # dataset.from # SQL: SELECT *
249
- # dataset.from(:blah) # SQL: SELECT * FROM blah
250
- # dataset.from(:blah, :foo) # SQL: SELECT * FROM blah, foo
251
- def from(*source)
252
- table_alias_num = 0
253
- sources = []
254
- source.each do |s|
255
- case s
256
- when Hash
257
- s.each{|k,v| sources << SQL::AliasedExpression.new(k,v)}
258
- when Dataset
259
- sources << SQL::AliasedExpression.new(s, dataset_alias(table_alias_num+=1))
260
- when Symbol
261
- sch, table, aliaz = split_symbol(s)
262
- if aliaz
263
- s = sch ? SQL::QualifiedIdentifier.new(sch.to_sym, table.to_sym) : SQL::Identifier.new(table.to_sym)
264
- sources << SQL::AliasedExpression.new(s, aliaz.to_sym)
265
- else
266
- sources << s
267
- end
268
- else
269
- sources << s
270
- end
271
- end
272
- o = {:from=>sources.empty? ? nil : sources}
273
- o[:num_dataset_sources] = table_alias_num if table_alias_num > 0
274
- clone(o)
275
- end
276
-
277
- # Returns a dataset selecting from the current dataset.
278
- # Supplying the :alias option controls the name of the result.
279
- #
280
- # ds = DB[:items].order(:name).select(:id, :name)
281
- # ds.sql #=> "SELECT id,name FROM items ORDER BY name"
282
- # ds.from_self.sql #=> "SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS 't1'"
283
- # ds.from_self(:alias=>:foo).sql #=> "SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS 'foo'"
284
- def from_self(opts={})
285
- fs = {}
286
- @opts.keys.each{|k| fs[k] = nil unless FROM_SELF_KEEP_OPTS.include?(k)}
287
- clone(fs).from(opts[:alias] ? as(opts[:alias]) : self)
288
- end
289
-
290
158
  # SQL fragment specifying an SQL function call
291
159
  def function_sql(f)
292
160
  args = f.args
293
161
  "#{f.f}#{args.empty? ? '()' : literal(args)}"
294
162
  end
295
163
 
296
- # Pattern match any of the columns to any of the terms. The terms can be
297
- # strings (which use LIKE) or regular expressions (which are only supported
298
- # in some databases). See Sequel::SQL::StringExpression.like. Note that the
299
- # total number of pattern matches will be cols.length * terms.length,
300
- # which could cause performance issues.
301
- #
302
- # dataset.grep(:a, '%test%') # SQL: SELECT * FROM items WHERE a LIKE '%test%'
303
- # dataset.grep([:a, :b], %w'%test% foo') # SQL: SELECT * FROM items WHERE a LIKE '%test%' OR a LIKE 'foo' OR b LIKE '%test%' OR b LIKE 'foo'
304
- def grep(cols, terms)
305
- filter(SQL::BooleanExpression.new(:OR, *Array(cols).collect{|c| SQL::StringExpression.like(c, *terms)}))
306
- end
307
-
308
- # Returns a copy of the dataset with the results grouped by the value of
309
- # the given columns.
310
- #
311
- # dataset.group(:id) # SELECT * FROM items GROUP BY id
312
- # dataset.group(:id, :name) # SELECT * FROM items GROUP BY id, name
313
- def group(*columns)
314
- clone(:group => (columns.compact.empty? ? nil : columns))
315
- end
316
- alias group_by group
317
-
318
- # Returns a copy of the dataset with the HAVING conditions changed. Raises
319
- # an error if the dataset has not been grouped. See #filter for argument types.
320
- #
321
- # dataset.group(:sum).having(:sum=>10) # SQL: SELECT * FROM items GROUP BY sum HAVING sum = 10
322
- def having(*cond, &block)
323
- raise(InvalidOperation, "Can only specify a HAVING clause on a grouped dataset") unless @opts[:group]
324
- _filter(:having, *cond, &block)
325
- end
326
-
327
164
  # Inserts multiple values. If a block is given it is invoked for each
328
165
  # item in the given array before inserting it. See #multi_insert as
329
166
  # a possible faster version that inserts multiple records in one
@@ -390,36 +227,6 @@ module Sequel
390
227
  clone(:columns=>columns, :values=>values)._insert_sql
391
228
  end
392
229
 
393
- # Adds an INTERSECT clause using a second dataset object.
394
- # An INTERSECT compound dataset returns all rows in both the current dataset
395
- # and the given dataset.
396
- # Raises an InvalidOperation if the operation is not supported.
397
- # Options:
398
- # * :all - Set to true to use INTERSECT ALL instead of INTERSECT, so duplicate rows can occur
399
- # * :from_self - Set to false to not wrap the returned dataset in a from_self, use with care.
400
- #
401
- # DB[:items].intersect(DB[:other_items]).sql
402
- # #=> "SELECT * FROM items INTERSECT SELECT * FROM other_items"
403
- def intersect(dataset, opts={})
404
- opts = {:all=>opts} unless opts.is_a?(Hash)
405
- raise(InvalidOperation, "INTERSECT not supported") unless supports_intersect_except?
406
- raise(InvalidOperation, "INTERSECT ALL not supported") if opts[:all] && !supports_intersect_except_all?
407
- compound_clone(:intersect, dataset, opts)
408
- end
409
-
410
- # Inverts the current filter
411
- #
412
- # dataset.filter(:category => 'software').invert.sql #=>
413
- # "SELECT * FROM items WHERE (category != 'software')"
414
- def invert
415
- having, where = @opts[:having], @opts[:where]
416
- raise(Error, "No current filter") unless having || where
417
- o = {}
418
- o[:having] = SQL::BooleanExpression.invert(having) if having
419
- o[:where] = SQL::BooleanExpression.invert(where) if where
420
- clone(o)
421
- end
422
-
423
230
  # SQL fragment specifying a JOIN clause without ON or USING.
424
231
  def join_clause_sql(jc)
425
232
  table = jc.table
@@ -470,6 +277,13 @@ module Sequel
470
277
  # the table alias/name for the last joined (or first table), and an array of previous
471
278
  # SQL::JoinClause.
472
279
  def join_table(type, table, expr=nil, options={}, &block)
280
+ using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
281
+ if using_join && !supports_join_using?
282
+ h = {}
283
+ expr.each{|s| h[s] = s}
284
+ return join_table(type, table, h, options)
285
+ end
286
+
473
287
  if [Symbol, String].any?{|c| options.is_a?(c)}
474
288
  table_alias = options
475
289
  last_alias = nil
@@ -490,7 +304,7 @@ module Sequel
490
304
 
491
305
  join = if expr.nil? and !block_given?
492
306
  SQL::JoinClause.new(type, table, table_alias)
493
- elsif Array === expr and !expr.empty? and expr.all?{|x| Symbol === x}
307
+ elsif using_join
494
308
  raise(Sequel::Error, "can't use a block if providing an array of symbols as expr") if block_given?
495
309
  SQL::JoinUsingClause.new(expr, type, table, table_alias)
496
310
  else
@@ -514,30 +328,6 @@ module Sequel
514
328
  clone(opts)
515
329
  end
516
330
 
517
- # If given an integer, the dataset will contain only the first l results.
518
- # If given a range, it will contain only those at offsets within that
519
- # range. If a second argument is given, it is used as an offset.
520
- #
521
- # dataset.limit(10) # SQL: SELECT * FROM items LIMIT 10
522
- # dataset.limit(10, 20) # SQL: SELECT * FROM items LIMIT 10 OFFSET 20
523
- def limit(l, o = nil)
524
- return from_self.limit(l, o) if @opts[:sql]
525
-
526
- if Range === l
527
- o = l.first
528
- l = l.last - l.first + (l.exclude_end? ? 0 : 1)
529
- end
530
- l = l.to_i
531
- raise(Error, 'Limits must be greater than or equal to 1') unless l >= 1
532
- opts = {:limit => l}
533
- if o
534
- o = o.to_i
535
- raise(Error, 'Offsets must be greater than or equal to 0') unless o >= 0
536
- opts[:offset] = o
537
- end
538
- clone(opts)
539
- end
540
-
541
331
  # Returns a literal representation of a value to be used as part
542
332
  # of an SQL expression.
543
333
  #
@@ -596,46 +386,6 @@ module Sequel
596
386
  values.map{|r| insert_sql(columns, r)}
597
387
  end
598
388
 
599
- # Adds an alternate filter to an existing filter using OR. If no filter
600
- # exists an error is raised.
601
- #
602
- # dataset.filter(:a).or(:b) # SQL: SELECT * FROM items WHERE a OR b
603
- def or(*cond, &block)
604
- clause = (@opts[:having] ? :having : :where)
605
- raise(InvalidOperation, "No existing filter found.") unless @opts[clause]
606
- cond = cond.first if cond.size == 1
607
- clone(clause => SQL::BooleanExpression.new(:OR, @opts[clause], filter_expr(cond, &block)))
608
- end
609
-
610
- # Returns a copy of the dataset with the order changed. If a nil is given
611
- # the returned dataset has no order. This can accept multiple arguments
612
- # of varying kinds, and even SQL functions. If a block is given, it is treated
613
- # as a virtual row block, similar to filter.
614
- #
615
- # ds.order(:name).sql #=> 'SELECT * FROM items ORDER BY name'
616
- # ds.order(:a, :b).sql #=> 'SELECT * FROM items ORDER BY a, b'
617
- # ds.order('a + b'.lit).sql #=> 'SELECT * FROM items ORDER BY a + b'
618
- # ds.order(:a + :b).sql #=> 'SELECT * FROM items ORDER BY (a + b)'
619
- # ds.order(:name.desc).sql #=> 'SELECT * FROM items ORDER BY name DESC'
620
- # ds.order(:name.asc).sql #=> 'SELECT * FROM items ORDER BY name ASC'
621
- # ds.order{|o| o.sum(:name)}.sql #=> 'SELECT * FROM items ORDER BY sum(name)'
622
- # ds.order(nil).sql #=> 'SELECT * FROM items'
623
- def order(*columns, &block)
624
- columns += Array(Sequel.virtual_row(&block)) if block
625
- clone(:order => (columns.compact.empty?) ? nil : columns)
626
- end
627
- alias_method :order_by, :order
628
-
629
- # Returns a copy of the dataset with the order columns added
630
- # to the existing order.
631
- #
632
- # ds.order(:a).order(:b).sql #=> 'SELECT * FROM items ORDER BY b'
633
- # ds.order(:a).order_more(:b).sql #=> 'SELECT * FROM items ORDER BY a, b'
634
- def order_more(*columns, &block)
635
- columns = @opts[:order] + columns if @opts[:order]
636
- order(*columns, &block)
637
- end
638
-
639
389
  # SQL fragment for the ordered expression, used in the ORDER BY
640
390
  # clause.
641
391
  def ordered_expression_sql(oe)
@@ -644,8 +394,14 @@ module Sequel
644
394
 
645
395
  # SQL fragment for a literal string with placeholders
646
396
  def placeholder_literal_string_sql(pls)
647
- args = pls.args.dup
648
- s = pls.str.gsub(QUESTION_MARK){literal(args.shift)}
397
+ args = pls.args
398
+ s = if args.is_a?(Hash)
399
+ re = /:(#{args.keys.map{|k| Regexp.escape(k.to_s)}.join('|')})\b/
400
+ pls.str.gsub(re){literal(args[$1.to_sym])}
401
+ else
402
+ i = -1
403
+ pls.str.gsub(QUESTION_MARK){literal(args.at(i+=1))}
404
+ end
649
405
  s = "(#{s})" if pls.parens
650
406
  s
651
407
  end
@@ -710,13 +466,6 @@ module Sequel
710
466
  "\"#{name.to_s.gsub('"', '""')}\""
711
467
  end
712
468
 
713
- # Returns a copy of the dataset with the order reversed. If no order is
714
- # given, the existing order is inverted.
715
- def reverse_order(*order)
716
- order(*invert_order(order.empty? ? @opts[:order] : order))
717
- end
718
- alias reverse reverse_order
719
-
720
469
  # Split the schema information from the table
721
470
  def schema_and_table(table_name)
722
471
  sch = db.default_schema if db
@@ -735,39 +484,6 @@ module Sequel
735
484
  end
736
485
  end
737
486
 
738
- # Returns a copy of the dataset with the columns selected changed
739
- # to the given columns. This also takes a virtual row block,
740
- # similar to filter.
741
- #
742
- # dataset.select(:a) # SELECT a FROM items
743
- # dataset.select(:a, :b) # SELECT a, b FROM items
744
- # dataset.select{|o| o.a, o.sum(:b)} # SELECT a, sum(b) FROM items
745
- def select(*columns, &block)
746
- columns += Array(Sequel.virtual_row(&block)) if block
747
- m = []
748
- columns.map do |i|
749
- i.is_a?(Hash) ? m.concat(i.map{|k, v| SQL::AliasedExpression.new(k,v)}) : m << i
750
- end
751
- clone(:select => m)
752
- end
753
-
754
- # Returns a copy of the dataset selecting the wildcard.
755
- #
756
- # dataset.select(:a).select_all # SELECT * FROM items
757
- def select_all
758
- clone(:select => nil)
759
- end
760
-
761
- # Returns a copy of the dataset with the given columns added
762
- # to the existing selected columns.
763
- #
764
- # dataset.select(:a).select(:b) # SELECT b FROM items
765
- # dataset.select(:a).select_more(:b) # SELECT a, b FROM items
766
- def select_more(*columns, &block)
767
- columns = @opts[:select] + columns if @opts[:select]
768
- select(*columns, &block)
769
- end
770
-
771
487
  # Formats a SELECT statement
772
488
  #
773
489
  # dataset.select_sql # => "SELECT * FROM items"
@@ -797,48 +513,6 @@ module Sequel
797
513
  end
798
514
  end
799
515
 
800
- # Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
801
- #
802
- # dataset.group(:a).having(:a=>1).where(:b).unfiltered # SELECT * FROM items GROUP BY a
803
- def unfiltered
804
- clone(:where => nil, :having => nil)
805
- end
806
-
807
- # Returns a copy of the dataset with no grouping (GROUP or HAVING clause) applied.
808
- #
809
- # dataset.group(:a).having(:a=>1).where(:b).ungrouped # SELECT * FROM items WHERE b
810
- def ungrouped
811
- clone(:group => nil, :having => nil)
812
- end
813
-
814
- # Adds a UNION clause using a second dataset object.
815
- # A UNION compound dataset returns all rows in either the current dataset
816
- # or the given dataset.
817
- # Options:
818
- # * :all - Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
819
- # * :from_self - Set to false to not wrap the returned dataset in a from_self, use with care.
820
- #
821
- # DB[:items].union(DB[:other_items]).sql
822
- # #=> "SELECT * FROM items UNION SELECT * FROM other_items"
823
- def union(dataset, opts={})
824
- opts = {:all=>opts} unless opts.is_a?(Hash)
825
- compound_clone(:union, dataset, opts)
826
- end
827
-
828
- # Returns a copy of the dataset with no limit or offset.
829
- #
830
- # dataset.limit(10, 20).unlimited # SELECT * FROM items
831
- def unlimited
832
- clone(:limit=>nil, :offset=>nil)
833
- end
834
-
835
- # Returns a copy of the dataset with no order.
836
- #
837
- # dataset.order(:a).unordered # SELECT * FROM items
838
- def unordered
839
- order(nil)
840
- end
841
-
842
516
  # Formats an UPDATE statement using the given values.
843
517
  #
844
518
  # dataset.update_sql(:price => 100, :category => 'software') #=>
@@ -913,9 +587,12 @@ module Sequel
913
587
  clone(:sql=>sql)
914
588
  end
915
589
 
916
- [:inner, :full_outer, :right_outer, :left_outer].each do |jtype|
590
+ CONDITIONED_JOIN_TYPES.each do |jtype|
917
591
  class_eval("def #{jtype}_join(*args, &block); join_table(:#{jtype}, *args, &block) end", __FILE__, __LINE__)
918
592
  end
593
+ UNCONDITIONED_JOIN_TYPES.each do |jtype|
594
+ class_eval("def #{jtype}_join(table); raise(Sequel::Error, '#{jtype}_join does not accept join table blocks') if block_given?; join_table(:#{jtype}, table) end", __FILE__, __LINE__)
595
+ end
919
596
  alias join inner_join
920
597
 
921
598
  protected
@@ -938,20 +615,20 @@ module Sequel
938
615
 
939
616
  private
940
617
 
941
- # Internal filter method so it works on either the having or where clauses.
942
- def _filter(clause, *cond, &block)
943
- cond = cond.first if cond.size == 1
944
- cond = filter_expr(cond, &block)
945
- cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
946
- clone(clause => cond)
947
- end
948
-
949
618
  # Formats the truncate statement. Assumes the table given has already been
950
619
  # literalized.
951
620
  def _truncate_sql(table)
952
621
  "TRUNCATE TABLE #{table}"
953
622
  end
954
623
 
624
+ # Clone of this dataset usable in aggregate operations. Does
625
+ # a from_self if dataset contains any parameters that would
626
+ # affect normal aggregation, or just removes an existing
627
+ # order if not.
628
+ def aggregate_dataset
629
+ options_overlap(COUNT_FROM_SELF_OPTS) ? from_self : unordered
630
+ end
631
+
955
632
  # Do a simple join of the arguments (which should be strings or symbols) separated by commas
956
633
  def argument_list(args)
957
634
  args.join(COMMA_SEPARATOR)
@@ -982,12 +659,6 @@ module Sequel
982
659
  (columns.nil? || columns.empty?) ? WILDCARD : expression_list(columns)
983
660
  end
984
661
 
985
- # Add the dataset to the list of compounds
986
- def compound_clone(type, dataset, opts)
987
- ds = compound_from_self.clone(:compounds=>Array(@opts[:compounds]).map{|x| x.dup} + [[type, dataset.compound_from_self, opts[:all]]])
988
- opts[:from_self] == false ? ds : ds.from_self
989
- end
990
-
991
662
  # The alias to use for datasets, takes a number to make sure the name is unique.
992
663
  def dataset_alias(number)
993
664
  :"#{DATASET_ALIAS_BASE_NAME}#{number}"
@@ -1009,40 +680,6 @@ module Sequel
1009
680
  requires_sql_standard_datetimes? ? STANDARD_TIMESTAMP_FORMAT : TIMESTAMP_FORMAT
1010
681
  end
1011
682
 
1012
- # SQL fragment based on the expr type. See #filter.
1013
- def filter_expr(expr = nil, &block)
1014
- expr = nil if expr == []
1015
- if expr && block
1016
- return SQL::BooleanExpression.new(:AND, filter_expr(expr), filter_expr(block))
1017
- elsif block
1018
- expr = block
1019
- end
1020
- case expr
1021
- when Hash
1022
- SQL::BooleanExpression.from_value_pairs(expr)
1023
- when Array
1024
- if String === expr[0]
1025
- SQL::PlaceholderLiteralString.new(expr.shift, expr, true)
1026
- elsif Sequel.condition_specifier?(expr)
1027
- SQL::BooleanExpression.from_value_pairs(expr)
1028
- else
1029
- SQL::BooleanExpression.new(:AND, *expr.map{|x| filter_expr(x)})
1030
- end
1031
- when Proc
1032
- filter_expr(Sequel.virtual_row(&expr))
1033
- when SQL::NumericExpression, SQL::StringExpression
1034
- raise(Error, "Invalid SQL Expression type: #{expr.inspect}")
1035
- when Symbol, SQL::Expression
1036
- expr
1037
- when TrueClass, FalseClass
1038
- SQL::BooleanExpression.new(:NOOP, expr)
1039
- when String
1040
- LiteralString.new("(#{expr})")
1041
- else
1042
- raise(Error, 'Invalid filter argument')
1043
- end
1044
- end
1045
-
1046
683
  # Format the timestamp based on the default_timestamp_format, with a couple
1047
684
  # of modifiers. First, allow %N to be used for fractions seconds (if the
1048
685
  # database supports them), and override %z to always use a numeric offset
@@ -1111,25 +748,6 @@ module Sequel
1111
748
  end
1112
749
  end
1113
750
 
1114
- # Inverts the given order by breaking it into a list of column references
1115
- # and inverting them.
1116
- #
1117
- # dataset.invert_order([:id.desc]]) #=> [:id]
1118
- # dataset.invert_order(:category, :price.desc]) #=>
1119
- # [:category.desc, :price]
1120
- def invert_order(order)
1121
- return nil unless order
1122
- new_order = []
1123
- order.map do |f|
1124
- case f
1125
- when SQL::OrderedExpression
1126
- f.invert
1127
- else
1128
- SQL::OrderedExpression.new(f)
1129
- end
1130
- end
1131
- end
1132
-
1133
751
  # SQL fragment specifying a JOIN type, converts underscores to
1134
752
  # spaces and upcases.
1135
753
  def join_type_sql(join_type)
@@ -1192,11 +810,18 @@ module Sequel
1192
810
  v.to_s
1193
811
  end
1194
812
 
1195
- # SQL fragmento for a type of object not handled by Dataset#literal.
1196
- # Raises an error. If a database specific type is allowed,
1197
- # this should be overriden in a subclass.
813
+ # SQL fragment for a type of object not handled by Dataset#literal.
814
+ # Calls sql_literal if object responds to it, otherwise raises an error.
815
+ # Classes implementing sql_literal should call a class-specific method on the dataset
816
+ # provided and should add that method to Sequel::Dataset, allowing for adapters
817
+ # to provide customized literalizations.
818
+ # If a database specific type is allowed, this should be overriden in a subclass.
1198
819
  def literal_other(v)
1199
- raise Error, "can't express #{v.inspect} as a SQL literal"
820
+ if v.respond_to?(:sql_literal)
821
+ v.sql_literal(self)
822
+ else
823
+ raise Error, "can't express #{v.inspect} as a SQL literal"
824
+ end
1200
825
  end
1201
826
 
1202
827
  # SQL fragment for String. Doubles \ and ' by default.
@@ -1293,7 +918,14 @@ module Sequel
1293
918
  o[:order] = qualified_expression(o[:order], table) if o[:order]
1294
919
  SQL::Window.new(o)
1295
920
  when SQL::PlaceholderLiteralString
1296
- SQL::PlaceholderLiteralString.new(e.str, qualified_expression(e.args, table), e.parens)
921
+ args = if e.args.is_a?(Hash)
922
+ h = {}
923
+ e.args.each{|k,v| h[k] = qualified_expression(v, table)}
924
+ h
925
+ else
926
+ qualified_expression(e.args, table)
927
+ end
928
+ SQL::PlaceholderLiteralString.new(e.str, args, e.parens)
1297
929
  else
1298
930
  e
1299
931
  end