sequel 3.27.0 → 3.28.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 (73) hide show
  1. data/CHANGELOG +96 -0
  2. data/README.rdoc +2 -2
  3. data/Rakefile +1 -1
  4. data/doc/association_basics.rdoc +48 -0
  5. data/doc/opening_databases.rdoc +29 -5
  6. data/doc/prepared_statements.rdoc +1 -0
  7. data/doc/release_notes/3.28.0.txt +304 -0
  8. data/doc/testing.rdoc +42 -0
  9. data/doc/transactions.rdoc +97 -0
  10. data/lib/sequel/adapters/db2.rb +95 -65
  11. data/lib/sequel/adapters/firebird.rb +25 -219
  12. data/lib/sequel/adapters/ibmdb.rb +440 -0
  13. data/lib/sequel/adapters/jdbc.rb +12 -0
  14. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  15. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  16. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  18. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  19. data/lib/sequel/adapters/mysql.rb +10 -15
  20. data/lib/sequel/adapters/odbc.rb +1 -2
  21. data/lib/sequel/adapters/odbc/db2.rb +5 -5
  22. data/lib/sequel/adapters/postgres.rb +71 -11
  23. data/lib/sequel/adapters/shared/db2.rb +290 -0
  24. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +18 -75
  26. data/lib/sequel/adapters/shared/mysql.rb +13 -0
  27. data/lib/sequel/adapters/shared/postgres.rb +52 -36
  28. data/lib/sequel/adapters/shared/sqlite.rb +32 -36
  29. data/lib/sequel/adapters/sqlite.rb +4 -8
  30. data/lib/sequel/adapters/tinytds.rb +7 -3
  31. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  32. data/lib/sequel/core.rb +1 -1
  33. data/lib/sequel/database/connecting.rb +1 -1
  34. data/lib/sequel/database/misc.rb +6 -5
  35. data/lib/sequel/database/query.rb +1 -1
  36. data/lib/sequel/database/schema_generator.rb +2 -1
  37. data/lib/sequel/dataset/actions.rb +149 -33
  38. data/lib/sequel/dataset/features.rb +44 -7
  39. data/lib/sequel/dataset/misc.rb +9 -1
  40. data/lib/sequel/dataset/prepared_statements.rb +2 -2
  41. data/lib/sequel/dataset/query.rb +63 -10
  42. data/lib/sequel/dataset/sql.rb +22 -5
  43. data/lib/sequel/model.rb +3 -3
  44. data/lib/sequel/model/associations.rb +250 -27
  45. data/lib/sequel/model/base.rb +10 -16
  46. data/lib/sequel/plugins/many_through_many.rb +34 -2
  47. data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
  48. data/lib/sequel/sql.rb +94 -51
  49. data/lib/sequel/version.rb +1 -1
  50. data/spec/adapters/db2_spec.rb +146 -0
  51. data/spec/adapters/postgres_spec.rb +74 -6
  52. data/spec/adapters/spec_helper.rb +1 -0
  53. data/spec/adapters/sqlite_spec.rb +11 -0
  54. data/spec/core/database_spec.rb +7 -0
  55. data/spec/core/dataset_spec.rb +180 -17
  56. data/spec/core/expression_filters_spec.rb +107 -41
  57. data/spec/core/spec_helper.rb +11 -0
  58. data/spec/extensions/many_through_many_spec.rb +115 -1
  59. data/spec/extensions/prepared_statements_with_pk_spec.rb +3 -3
  60. data/spec/integration/associations_test.rb +193 -15
  61. data/spec/integration/database_test.rb +4 -2
  62. data/spec/integration/dataset_test.rb +215 -19
  63. data/spec/integration/plugin_test.rb +8 -5
  64. data/spec/integration/prepared_statement_test.rb +91 -98
  65. data/spec/integration/schema_test.rb +27 -11
  66. data/spec/integration/spec_helper.rb +10 -0
  67. data/spec/integration/type_test.rb +2 -2
  68. data/spec/model/association_reflection_spec.rb +91 -0
  69. data/spec/model/associations_spec.rb +13 -0
  70. data/spec/model/base_spec.rb +8 -21
  71. data/spec/model/eager_loading_spec.rb +243 -9
  72. data/spec/model/model_spec.rb +15 -2
  73. metadata +16 -4
@@ -10,7 +10,7 @@ module Sequel
10
10
  TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'".freeze
11
11
  TEMP_STORE = [:default, :file, :memory].freeze
12
12
  VIEWS_FILTER = "type = 'view'".freeze
13
-
13
+
14
14
  # Run all alter_table commands in a transaction. This is technically only
15
15
  # needed for drop column.
16
16
  def alter_table(name, generator=nil, &block)
@@ -107,6 +107,19 @@ module Sequel
107
107
  sqlite_version >= 30608
108
108
  end
109
109
 
110
+ # Override the default setting for whether to use timezones in timestamps.
111
+ # For backwards compatibility, it is set to +true+ by default.
112
+ # Anyone wanting to use SQLite's datetime functions should set it to +false+
113
+ # using this method. It's possible that the default will change in a future version,
114
+ # so anyone relying on timezones in timestamps should set this to +true+.
115
+ attr_writer :use_timestamp_timezones
116
+
117
+ # SQLite supports timezones in timestamps, since it just stores them as strings,
118
+ # but it breaks the usage of SQLite's datetime functions.
119
+ def use_timestamp_timezones?
120
+ defined?(@use_timestamp_timezones) ? @use_timestamp_timezones : (@use_timestamp_timezones = true)
121
+ end
122
+
110
123
  # A symbol signifying the value of the synchronous PRAGMA.
111
124
  def synchronous
112
125
  SYNCHRONOUS[pragma_get(:synchronous).to_i]
@@ -339,23 +352,7 @@ module Sequel
339
352
  SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
340
353
  COMMA_SEPARATOR = ', '.freeze
341
354
  CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
342
-
343
- # Ugly hack. Really, SQLite uses 0 for false and 1 for true
344
- # but then you can't differentiate between integers and booleans.
345
- # In filters, SQL::BooleanConstants are used more, while in other places
346
- # the ruby true/false values are used more, so use 1/0 for SQL::BooleanConstants.
347
- # The correct fix for this would require separate literalization paths for
348
- # filters compared to other values, but that's more work than I want to do right now.
349
- def boolean_constant_sql(constant)
350
- case constant
351
- when true
352
- '1'
353
- when false
354
- '0'
355
- else
356
- super
357
- end
358
- end
355
+ EXTRACT_MAP = {:year=>"'%Y'", :month=>"'%m'", :day=>"'%d'", :hour=>"'%H'", :minute=>"'%M'", :second=>"'%f'"}
359
356
 
360
357
  # SQLite does not support pattern matching via regular expressions.
361
358
  # SQLite is case insensitive (depending on pragma), so use LIKE for
@@ -367,6 +364,15 @@ module Sequel
367
364
  when :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
368
365
  # SQLite is case insensitive for ASCII, and non case sensitive for other character sets
369
366
  "#{'NOT ' if [:'NOT LIKE', :'NOT ILIKE'].include?(op)}(#{literal(args.at(0))} LIKE #{literal(args.at(1))})"
367
+ when :^
368
+ a = literal(args.at(0))
369
+ b = literal(args.at(1))
370
+ "((~(#{a} & #{b})) & (#{a} | #{b}))"
371
+ when :extract
372
+ part = args.at(0)
373
+ raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
374
+ expr = args.at(1)
375
+ "CAST(strftime(#{format}, #{literal(expr)}) AS #{part == :second ? 'NUMERIC' : 'INTEGER'})"
370
376
  else
371
377
  super(op, args)
372
378
  end
@@ -418,11 +424,17 @@ module Sequel
418
424
  end
419
425
 
420
426
  # SQLite supports timezones in literal timestamps, since it stores them
421
- # as text.
427
+ # as text. But using timezones in timestamps breaks SQLite datetime
428
+ # functions, so we allow the user to override the default per database.
422
429
  def supports_timestamp_timezones?
423
- true
430
+ db.use_timestamp_timezones?
424
431
  end
425
432
 
433
+ # SQLite cannot use WHERE 't'.
434
+ def supports_where_true?
435
+ false
436
+ end
437
+
426
438
  private
427
439
 
428
440
  # SQLite uses string literals instead of identifiers in AS clauses.
@@ -431,22 +443,6 @@ module Sequel
431
443
  "#{expression} AS #{literal(aliaz.to_s)}"
432
444
  end
433
445
 
434
- # Special case when true or false is provided directly to filter.
435
- def filter_expr(expr)
436
- if block_given?
437
- super
438
- else
439
- case expr
440
- when true
441
- 1
442
- when false
443
- 0
444
- else
445
- super
446
- end
447
- end
448
- end
449
-
450
446
  # SQL fragment specifying a list of identifiers
451
447
  def identifier_list(columns)
452
448
  columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
@@ -13,26 +13,22 @@ module Sequel
13
13
  TYPE_TRANSLATOR = tt = Class.new do
14
14
  FALSE_VALUES = %w'0 false f no n'.freeze
15
15
  def boolean(s) !FALSE_VALUES.include?(s.downcase) end
16
- def blob(s) ::Sequel::SQL::Blob.new(s) end
17
16
  def integer(s) s.to_i end
18
17
  def float(s) s.to_f end
19
18
  def numeric(s) ::BigDecimal.new(s) rescue s end
20
- def date(s) ::Sequel.string_to_date(s) end
21
- def time(s) ::Sequel.string_to_time(s) end
22
- def timestamp(s) ::Sequel.database_to_application_timestamp(s) end
23
19
  end.new
24
20
 
25
21
  # Hash with string keys and callable values for converting SQLite types.
26
22
  SQLITE_TYPES = {}
27
23
  {
28
- %w'timestamp datetime' => tt.method(:timestamp),
29
- %w'date' => tt.method(:date),
30
- %w'time' => tt.method(:time),
24
+ %w'timestamp datetime' => ::Sequel.method(:database_to_application_timestamp),
25
+ %w'date' => ::Sequel.method(:string_to_date),
26
+ %w'time' => ::Sequel.method(:string_to_time),
31
27
  %w'bit bool boolean' => tt.method(:boolean),
32
28
  %w'integer smallint mediumint int bigint' => tt.method(:integer),
33
29
  %w'numeric decimal money' => tt.method(:numeric),
34
30
  %w'float double real dec fixed' + ['double precision'] => tt.method(:float),
35
- %w'blob' => tt.method(:blob)
31
+ %w'blob' => ::Sequel::SQL::Blob.method(:new)
36
32
  }.each do |k,v|
37
33
  k.each{|n| SQLITE_TYPES[n] = v}
38
34
  end
@@ -124,9 +124,13 @@ module Sequel
124
124
  [v, 'double precision']
125
125
  when Numeric
126
126
  [v, 'numeric']
127
- when SQLTime
128
- [literal(v), 'time']
129
- when DateTime, Time
127
+ when Time
128
+ if v.is_a?(SQLTime)
129
+ [literal(v), 'time']
130
+ else
131
+ [literal(v), 'datetime']
132
+ end
133
+ when DateTime
130
134
  [literal(v), 'datetime']
131
135
  when Date
132
136
  [literal(v), 'date']
@@ -0,0 +1,55 @@
1
+ module Sequel
2
+ module EmulateOffsetWithRowNumber
3
+ # When a subselect that uses :offset is used in IN or NOT IN,
4
+ # use a nested subselect that only includes the first column
5
+ # instead of the ROW_NUMBER column added by the emulated offset support.
6
+ def complex_expression_sql(op, args)
7
+ case op
8
+ when :IN, :"NOT IN"
9
+ ds = args.at(1)
10
+ if ds.is_a?(Sequel::Dataset) && ds.opts[:offset]
11
+ c = ds.opts[:select].first
12
+ case c
13
+ when Symbol
14
+ t, cl, a = split_symbol(c)
15
+ if a
16
+ c = SQL::Identifier.new(a)
17
+ elsif t
18
+ c = SQL::Identifier.new(cl)
19
+ end
20
+ when SQL::AliasedExpression
21
+ c = SQL::Identifier.new(c.aliaz)
22
+ when SQL::QualifiedIdentifier
23
+ c = SQL::Identifier.new(c.column)
24
+ end
25
+ super(op, [args.at(0), ds.from_self.select(c)])
26
+ else
27
+ super
28
+ end
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ # Emulate OFFSET support with the ROW_NUMBER window function
35
+ #
36
+ # The implementation is ugly, cloning the current dataset and modifying
37
+ # the clone to add a ROW_NUMBER window function (and some other things),
38
+ # then using the modified clone in a subselect which is selected from.
39
+ #
40
+ # If offset is used, an order must be provided, because the use of ROW_NUMBER
41
+ # requires an order.
42
+ def select_sql
43
+ return super unless o = @opts[:offset]
44
+ raise(Error, "#{db.database_type} requires an order be provided if using an offset") unless order = @opts[:order]
45
+ dsa1 = dataset_alias(1)
46
+ rn = row_number_column
47
+ subselect_sql(unlimited.
48
+ unordered.
49
+ select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
50
+ from_self(:alias=>dsa1).
51
+ limit(@opts[:limit]).
52
+ where(SQL::Identifier.new(rn) > o))
53
+ end
54
+ end
55
+ end
@@ -188,7 +188,7 @@ module Sequel
188
188
  # This is used to ensure that the files loaded are from the same version of
189
189
  # Sequel as this file.
190
190
  def self.require(files, subdir=nil)
191
- Array(files).each{|f| super("#{File.dirname(__FILE__)}/#{"#{subdir}/" if subdir}#{f}")}
191
+ Array(files).each{|f| super("#{File.dirname(__FILE__).untaint}/#{"#{subdir}/" if subdir}#{f}")}
192
192
  end
193
193
 
194
194
  # Set whether to set the single threaded mode for all databases by default. By default,
@@ -6,7 +6,7 @@ module Sequel
6
6
  # ---------------------
7
7
 
8
8
  # Array of supported database adapters
9
- ADAPTERS = %w'ado amalgalite db2 dbi do firebird informix jdbc mysql mysql2 odbc openbase oracle postgres sqlite swift tinytds'.collect{|x| x.to_sym}
9
+ ADAPTERS = %w'ado amalgalite db2 dbi do firebird ibmdb informix jdbc mysql mysql2 odbc openbase oracle postgres sqlite swift tinytds'.collect{|x| x.to_sym}
10
10
 
11
11
  # Whether to use the single threaded connection pool by default
12
12
  @@single_threaded = false
@@ -275,15 +275,16 @@ module Sequel
275
275
  # Typecast the value to a Time
276
276
  def typecast_value_time(value)
277
277
  case value
278
- when SQLTime
279
- value
280
278
  when Time
281
- SQLTime.local(value.year, value.month, value.day, value.hour, value.min, value.sec, value.respond_to?(:nsec) ? value.nsec : value.usec)
279
+ if value.is_a?(SQLTime)
280
+ value
281
+ else
282
+ SQLTime.create(value.hour, value.min, value.sec, value.respond_to?(:nsec) ? value.nsec/1000.0 : value.usec)
283
+ end
282
284
  when String
283
285
  Sequel.string_to_time(value)
284
286
  when Hash
285
- t = Time.now
286
- SQLTime.local(t.year, t.month, t.day, *[:hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
287
+ SQLTime.create(*[:hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
287
288
  else
288
289
  raise Sequel::InvalidValue, "invalid value for Time: #{value.inspect}"
289
290
  end
@@ -471,7 +471,7 @@ module Sequel
471
471
  :float
472
472
  when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+)\))?)|(?:small)?money)\z/io
473
473
  $1 && $1 == '0' ? :integer : :decimal
474
- when /bytea|blob|image|(var)?binary/io
474
+ when /bytea|[bc]lob|image|(var)?binary/io
475
475
  :blob
476
476
  when /\Aenum/io
477
477
  :enum
@@ -86,7 +86,8 @@ module Sequel
86
86
  # :index :: Create an index on this column.
87
87
  # :key :: For foreign key columns, the column in the associated table
88
88
  # that this column references. Unnecessary if this column
89
- # references the primary key of the associated table.
89
+ # references the primary key of the associated table, except if you are
90
+ # using MySQL.
90
91
  # :null :: Mark the column as allowing NULL values (if true),
91
92
  # or not allowing NULL values (if false). If unspecified, will default
92
93
  # to whatever the database default is.
@@ -101,10 +101,15 @@ module Sequel
101
101
  #
102
102
  # DB[:table].delete # DELETE * FROM table
103
103
  # # => 3
104
- def delete
105
- execute_dui(delete_sql)
104
+ def delete(&block)
105
+ sql = delete_sql
106
+ if uses_returning?(:delete)
107
+ returning_fetch_rows(sql, &block)
108
+ else
109
+ execute_dui(sql)
110
+ end
106
111
  end
107
-
112
+
108
113
  # Iterates over the records in the dataset as they are yielded from the
109
114
  # database adapter, and returns self.
110
115
  #
@@ -279,8 +284,13 @@ module Sequel
279
284
  #
280
285
  # DB[:items].insert([:a, :b], DB[:old_items])
281
286
  # # INSERT INTO items (a, b) SELECT * FROM old_items
282
- def insert(*values)
283
- execute_insert(insert_sql(*values))
287
+ def insert(*values, &block)
288
+ sql = insert_sql(*values)
289
+ if uses_returning?(:insert)
290
+ returning_fetch_rows(sql, &block)
291
+ else
292
+ execute_insert(sql)
293
+ end
284
294
  end
285
295
 
286
296
  # Inserts multiple values. If a block is given it is invoked for each
@@ -336,10 +346,19 @@ module Sequel
336
346
  #
337
347
  # DB[:table].map{|r| r[:id] * 2} # SELECT * FROM table
338
348
  # # => [2, 4, 6, ...]
349
+ #
350
+ # You can also provide an array of column names:
351
+ #
352
+ # DB[:table].map([:id, :name]) # SELECT * FROM table
353
+ # # => [[1, 'A'], [2, 'B'], [3, 'C'], ...]
339
354
  def map(column=nil, &block)
340
355
  if column
341
356
  raise(Error, ARG_BLOCK_ERROR_MSG) if block
342
- super(){|r| r[column]}
357
+ if column.is_a?(Array)
358
+ super(){|r| column.map{|c| r[c]}}
359
+ else
360
+ super(){|r| r[column]}
361
+ end
343
362
  else
344
363
  super(&block)
345
364
  end
@@ -395,8 +414,24 @@ module Sequel
395
414
  #
396
415
  # DB[:table].select_hash(:id, :name) # SELECT id, name FROM table
397
416
  # # => {1=>'a', 2=>'b', ...}
417
+ #
418
+ # You can also provide an array of column names for either the key_column,
419
+ # the value column, or both:
420
+ #
421
+ # DB[:table].select_hash([:id, :foo], [:name, :bar]) # SELECT * FROM table
422
+ # # {[1, 3]=>['a', 'c'], [2, 4]=>['b', 'd'], ...}
398
423
  def select_hash(key_column, value_column)
399
- select(key_column, value_column).to_hash(hash_key_symbol(key_column), hash_key_symbol(value_column))
424
+ if key_column.is_a?(Array)
425
+ if value_column.is_a?(Array)
426
+ select(*(key_column + value_column)).to_hash(key_column.map{|c| hash_key_symbol(c)}, value_column.map{|c| hash_key_symbol(c)})
427
+ else
428
+ select(*(key_column + [value_column])).to_hash(key_column.map{|c| hash_key_symbol(c)}, hash_key_symbol(value_column))
429
+ end
430
+ elsif value_column.is_a?(Array)
431
+ select(key_column, *value_column).to_hash(hash_key_symbol(key_column), value_column.map{|c| hash_key_symbol(c)})
432
+ else
433
+ select(key_column, value_column).to_hash(hash_key_symbol(key_column), hash_key_symbol(value_column))
434
+ end
400
435
  end
401
436
 
402
437
  # Selects the column given (either as an argument or as a block), and
@@ -410,35 +445,32 @@ module Sequel
410
445
  #
411
446
  # DB[:table].select_map{id * 2} # SELECT (id * 2) FROM table
412
447
  # # => [6, 10, 16, 2, ...]
448
+ #
449
+ # You can also provide an array of column names:
450
+ #
451
+ # DB[:table].select_map([:id, :name]) # SELECT id, name FROM table
452
+ # # => [[1, 'A'], [2, 'B'], [3, 'C'], ...]
413
453
  def select_map(column=nil, &block)
414
- ds = naked.ungraphed
415
- ds = if column
416
- raise(Error, ARG_BLOCK_ERROR_MSG) if block
417
- ds.select(column)
418
- else
419
- ds.select(&block)
420
- end
421
- ds.map{|r| r.values.first}
454
+ _select_map(column, false, &block)
422
455
  end
456
+
423
457
 
424
458
  # The same as select_map, but in addition orders the array by the column.
425
459
  #
426
460
  # DB[:table].select_order_map(:id) # SELECT id FROM table ORDER BY id
427
461
  # # => [1, 2, 3, 4, ...]
428
462
  #
429
- # DB[:table].select_order_map{abs(id)} # SELECT (id * 2) FROM table ORDER BY (id * 2)
463
+ # DB[:table].select_order_map{id * 2} # SELECT (id * 2) FROM table ORDER BY (id * 2)
430
464
  # # => [2, 4, 6, 8, ...]
465
+ #
466
+ # You can also provide an array of column names:
467
+ #
468
+ # DB[:table].select_order_map([:id, :name]) # SELECT id, name FROM table ORDER BY id, name
469
+ # # => [[1, 'A'], [2, 'B'], [3, 'C'], ...]
431
470
  def select_order_map(column=nil, &block)
432
- ds = naked.ungraphed
433
- ds = if column
434
- raise(Error, ARG_BLOCK_ERROR_MSG) if block
435
- ds.select(column).order(unaliased_identifier(column))
436
- else
437
- ds.select(&block).order(&block)
438
- end
439
- ds.map{|r| r.values.first}
471
+ _select_map(column, true, &block)
440
472
  end
441
-
473
+
442
474
  # Alias for update, but not aliased directly so subclasses
443
475
  # don't have to override both methods.
444
476
  def set(*args)
@@ -502,11 +534,37 @@ module Sequel
502
534
  #
503
535
  # DB[:table].to_hash(:id) # SELECT * FROM table
504
536
  # # {1=>{:id=>1, :name=>'Jim'}, 2=>{:id=>2, :name=>'Bob'}, ...}
537
+ #
538
+ # You can also provide an array of column names for either the key_column,
539
+ # the value column, or both:
540
+ #
541
+ # DB[:table].to_hash([:id, :foo], [:name, :bar]) # SELECT * FROM table
542
+ # # {[1, 3]=>['Jim', 'bo'], [2, 4]=>['Bob', 'be'], ...}
543
+ #
544
+ # DB[:table].to_hash([:id, :name]) # SELECT * FROM table
545
+ # # {[1, 'Jim']=>{:id=>1, :name=>'Jim'}, [2, 'Bob'=>{:id=>2, :name=>'Bob'}, ...}
505
546
  def to_hash(key_column, value_column = nil)
506
- inject({}) do |m, r|
507
- m[r[key_column]] = value_column ? r[value_column] : r
508
- m
547
+ h = {}
548
+ if value_column
549
+ if value_column.is_a?(Array)
550
+ if key_column.is_a?(Array)
551
+ each{|r| h[key_column.map{|c| r[c]}] = value_column.map{|c| r[c]}}
552
+ else
553
+ each{|r| h[r[key_column]] = value_column.map{|c| r[c]}}
554
+ end
555
+ else
556
+ if key_column.is_a?(Array)
557
+ each{|r| h[key_column.map{|c| r[c]}] = r[value_column]}
558
+ else
559
+ each{|r| h[r[key_column]] = r[value_column]}
560
+ end
561
+ end
562
+ elsif key_column.is_a?(Array)
563
+ each{|r| h[key_column.map{|c| r[c]}] = r}
564
+ else
565
+ each{|r| h[r[key_column]] = r}
509
566
  end
567
+ h
510
568
  end
511
569
 
512
570
  # Truncates the dataset. Returns nil.
@@ -527,12 +585,38 @@ module Sequel
527
585
  #
528
586
  # DB[:table].update(:x=>:x+1, :y=>0) # UPDATE table SET x = (x + 1), y = 0
529
587
  # # => 10
530
- def update(values={})
531
- execute_dui(update_sql(values))
588
+ def update(values={}, &block)
589
+ sql = update_sql(values)
590
+ if uses_returning?(:update)
591
+ returning_fetch_rows(sql, &block)
592
+ else
593
+ execute_dui(sql)
594
+ end
532
595
  end
533
596
 
534
597
  private
535
598
 
599
+ # Internals of +select_map+ and +select_order_map+
600
+ def _select_map(column, order, &block)
601
+ ds = naked.ungraphed
602
+ if column
603
+ raise(Error, ARG_BLOCK_ERROR_MSG) if block
604
+ columns = Array(column)
605
+ select_cols = order ? columns.map{|c| c.is_a?(SQL::OrderedExpression) ? c.expression : c} : columns
606
+ ds = ds.select(*select_cols)
607
+ ds = ds.order(*columns.map{|c| unaliased_identifier(c)}) if order
608
+ else
609
+ ds = ds.select(&block)
610
+ ds = ds.order(&block) if order
611
+ end
612
+ if ds.opts[:select].length > 1
613
+ ret_cols = select_cols.map{|c| hash_key_symbol(c)}
614
+ ds.map{|r| ret_cols.map{|c| r[c]}}
615
+ else
616
+ ds.map{|r| r.values.first}
617
+ end
618
+ end
619
+
536
620
  # Set the server to use to :default unless it is already set in the passed opts
537
621
  def default_server_opts(opts)
538
622
  {:server=>@opts[:server] || :default}.merge(opts)
@@ -564,9 +648,19 @@ module Sequel
564
648
  # specifying the symbol that is likely to be used as the hash key
565
649
  # for the column when records are returned.
566
650
  def hash_key_symbol(s)
567
- raise(Error, "#{s.inspect} is not a symbol") unless s.is_a?(Symbol)
568
- _, c, a = split_symbol(s)
569
- (a || c).to_sym
651
+ case s
652
+ when Symbol
653
+ _, c, a = split_symbol(s)
654
+ (a || c).to_sym
655
+ when SQL::Identifier
656
+ hash_key_symbol(s.value)
657
+ when SQL::QualifiedIdentifier
658
+ hash_key_symbol(s.column)
659
+ when SQL::AliasedExpression
660
+ hash_key_symbol(s.aliaz)
661
+ else
662
+ raise(Error, "#{s.inspect} is not supported, should be a Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier, or SQL::AliasedExpression")
663
+ end
570
664
  end
571
665
 
572
666
  # Modify the identifier returned from the database based on the
@@ -583,6 +677,20 @@ module Sequel
583
677
  def post_load(all_records)
584
678
  end
585
679
 
680
+ # Called by insert/update/delete when returning is used.
681
+ # Yields each row as a plain hash to the block if one is given, or returns
682
+ # an array of plain hashes for all rows if a block is not given
683
+ def returning_fetch_rows(sql, &block)
684
+ if block
685
+ default_server.fetch_rows(sql, &block)
686
+ nil
687
+ else
688
+ rows = []
689
+ default_server.fetch_rows(sql){|r| rows << r}
690
+ rows
691
+ end
692
+ end
693
+
586
694
  # Return the unaliased part of the identifier. Handles both
587
695
  # implicit aliases in symbols, as well as SQL::AliasedExpression
588
696
  # objects. Other objects are returned as is.
@@ -593,6 +701,14 @@ module Sequel
593
701
  c_table ? SQL::QualifiedIdentifier.new(c_table, column.to_sym) : column.to_sym
594
702
  when SQL::AliasedExpression
595
703
  c.expression
704
+ when SQL::OrderedExpression
705
+ expr = c.expression
706
+ if expr.is_a?(Symbol)
707
+ expr = unaliased_identifier(expr)
708
+ SQL::OrderedExpression.new(unaliased_identifier(c.expression), c.descending, :nulls=>c.nulls)
709
+ else
710
+ c
711
+ end
596
712
  else
597
713
  c
598
714
  end