sequel 3.27.0 → 3.28.0

Sign up to get free protection for your applications and to get access to all the features.
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