sequel 5.36.0 → 5.41.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +56 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/doc/cheat_sheet.rdoc +5 -5
  6. data/doc/code_order.rdoc +0 -12
  7. data/doc/fork_safety.rdoc +84 -0
  8. data/doc/opening_databases.rdoc +5 -1
  9. data/doc/postgresql.rdoc +1 -1
  10. data/doc/querying.rdoc +3 -3
  11. data/doc/release_notes/5.37.0.txt +30 -0
  12. data/doc/release_notes/5.38.0.txt +28 -0
  13. data/doc/release_notes/5.39.0.txt +19 -0
  14. data/doc/release_notes/5.40.0.txt +40 -0
  15. data/doc/release_notes/5.41.0.txt +25 -0
  16. data/doc/sql.rdoc +1 -1
  17. data/doc/transactions.rdoc +0 -8
  18. data/lib/sequel/adapters/jdbc.rb +15 -3
  19. data/lib/sequel/adapters/jdbc/mysql.rb +4 -4
  20. data/lib/sequel/adapters/shared/mssql.rb +21 -1
  21. data/lib/sequel/adapters/shared/oracle.rb +1 -1
  22. data/lib/sequel/adapters/shared/postgres.rb +6 -4
  23. data/lib/sequel/adapters/shared/sqlite.rb +35 -1
  24. data/lib/sequel/core.rb +5 -6
  25. data/lib/sequel/database/connecting.rb +0 -1
  26. data/lib/sequel/database/misc.rb +14 -0
  27. data/lib/sequel/database/schema_generator.rb +6 -0
  28. data/lib/sequel/database/schema_methods.rb +16 -6
  29. data/lib/sequel/database/transactions.rb +1 -1
  30. data/lib/sequel/dataset/actions.rb +10 -6
  31. data/lib/sequel/dataset/features.rb +10 -0
  32. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  33. data/lib/sequel/dataset/sql.rb +32 -10
  34. data/lib/sequel/extensions/blank.rb +8 -0
  35. data/lib/sequel/extensions/date_arithmetic.rb +8 -9
  36. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  37. data/lib/sequel/extensions/inflector.rb +8 -0
  38. data/lib/sequel/extensions/migration.rb +9 -1
  39. data/lib/sequel/extensions/named_timezones.rb +5 -1
  40. data/lib/sequel/extensions/pg_array.rb +1 -0
  41. data/lib/sequel/extensions/pg_interval.rb +34 -8
  42. data/lib/sequel/extensions/pg_row.rb +1 -0
  43. data/lib/sequel/extensions/pg_row_ops.rb +24 -0
  44. data/lib/sequel/extensions/query.rb +2 -0
  45. data/lib/sequel/extensions/schema_dumper.rb +3 -3
  46. data/lib/sequel/model/associations.rb +28 -4
  47. data/lib/sequel/model/base.rb +21 -4
  48. data/lib/sequel/model/plugins.rb +5 -0
  49. data/lib/sequel/plugins/association_proxies.rb +2 -0
  50. data/lib/sequel/plugins/auto_validations.rb +15 -1
  51. data/lib/sequel/plugins/class_table_inheritance.rb +0 -5
  52. data/lib/sequel/plugins/composition.rb +5 -1
  53. data/lib/sequel/plugins/constraint_validations.rb +2 -1
  54. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  55. data/lib/sequel/plugins/dirty.rb +44 -0
  56. data/lib/sequel/plugins/nested_attributes.rb +3 -1
  57. data/lib/sequel/plugins/pg_array_associations.rb +4 -0
  58. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +2 -0
  59. data/lib/sequel/plugins/single_table_inheritance.rb +7 -0
  60. data/lib/sequel/plugins/tree.rb +9 -4
  61. data/lib/sequel/plugins/validation_helpers.rb +6 -2
  62. data/lib/sequel/timezones.rb +8 -3
  63. data/lib/sequel/version.rb +1 -1
  64. metadata +33 -21
@@ -51,7 +51,19 @@ module Sequel
51
51
  # Raise a Sequel::AdapterNotFound if evaluating the class name raises a NameError.
52
52
  def self.load_driver(drv, gem=nil)
53
53
  load_gem(gem) if gem
54
- eval drv
54
+ if drv.is_a?(String)
55
+ eval drv
56
+ else
57
+ *try, last = drv
58
+ try.each do |try_drv|
59
+ begin
60
+ return eval(try_drv)
61
+ rescue NameError
62
+ end
63
+ end
64
+
65
+ eval last
66
+ end
55
67
  rescue NameError
56
68
  raise Sequel::AdapterNotFound, "#{drv} not loaded#{", try installing jdbc-#{gem.to_s.downcase} gem" if gem}"
57
69
  end
@@ -59,11 +71,11 @@ module Sequel
59
71
  class TypeConvertor
60
72
  CONVERTORS = convertors = {}
61
73
  %w'Boolean Float Double Int Long Short'.each do |meth|
62
- x = convertors[meth.to_sym] = Object.new
74
+ x = x = convertors[meth.to_sym] = Object.new
63
75
  class_eval("def x.call(r, i) v = r.get#{meth}(i); v unless r.wasNull end", __FILE__, __LINE__)
64
76
  end
65
77
  %w'Object Array String Time Date Timestamp BigDecimal Blob Bytes Clob'.each do |meth|
66
- x = convertors[meth.to_sym] = Object.new
78
+ x = x = convertors[meth.to_sym] = Object.new
67
79
  class_eval("def x.call(r, i) r.get#{meth}(i) end", __FILE__, __LINE__)
68
80
  end
69
81
  x = convertors[:RubyTime] = Object.new
@@ -1,15 +1,15 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- Sequel::JDBC.load_driver('com.mysql.jdbc.Driver', :MySQL)
4
- require_relative '../shared/mysql'
5
-
6
3
  module Sequel
7
4
  module JDBC
5
+ driver = Sequel::JDBC.load_driver(%w'com.mysql.cj.jdbc.Driver com.mysql.jdbc.Driver', :MySQL)
6
+ require_relative '../shared/mysql'
7
+
8
8
  Sequel.synchronize do
9
9
  DATABASE_SETUP[:mysql] = proc do |db|
10
10
  db.extend(Sequel::JDBC::MySQL::DatabaseMethods)
11
11
  db.extend_datasets Sequel::MySQL::DatasetMethods
12
- com.mysql.jdbc.Driver
12
+ driver
13
13
  end
14
14
  end
15
15
 
@@ -244,6 +244,16 @@ module Sequel
244
244
 
245
245
  private
246
246
 
247
+ # Add CLUSTERED or NONCLUSTERED as needed
248
+ def add_clustered_sql_fragment(sql, opts)
249
+ clustered = opts[:clustered]
250
+ unless clustered.nil?
251
+ sql += " #{'NON' unless clustered}CLUSTERED"
252
+ end
253
+
254
+ sql
255
+ end
256
+
247
257
  # Add dropping of the default constraint to the list of SQL queries.
248
258
  # This is necessary before dropping the column or changing its type.
249
259
  def add_drop_default_constraint_sql(sqls, table, column)
@@ -284,7 +294,7 @@ module Sequel
284
294
  when :set_column_null
285
295
  sch = schema(table).find{|k,v| k.to_s == op[:name].to_s}.last
286
296
  type = sch[:db_type]
287
- if [:string, :decimal].include?(sch[:type]) && !["text", "ntext"].include?(type) && (size = (sch[:max_chars] || sch[:column_size]))
297
+ if [:string, :decimal, :blob].include?(sch[:type]) && !["text", "ntext"].include?(type) && (size = (sch[:max_chars] || sch[:column_size]))
288
298
  size = "MAX" if size == -1
289
299
  type += "(#{size}#{", #{sch[:scale]}" if sch[:scale] && sch[:scale].to_i > 0})"
290
300
  end
@@ -396,6 +406,11 @@ module Sequel
396
406
  super.with_quote_identifiers(true)
397
407
  end
398
408
 
409
+ # Handle clustered and nonclustered primary keys
410
+ def primary_key_constraint_sql_fragment(opts)
411
+ add_clustered_sql_fragment(super, opts)
412
+ end
413
+
399
414
  # Use sp_rename to rename the table
400
415
  def rename_table_sql(name, new_name)
401
416
  "sp_rename #{literal(quote_schema_table(name))}, #{quote_identifier(schema_and_table(new_name).pop)}"
@@ -492,6 +507,11 @@ module Sequel
492
507
  :'varbinary(max)'
493
508
  end
494
509
 
510
+ # Handle clustered and nonclustered unique constraints
511
+ def unique_constraint_sql_fragment(opts)
512
+ add_clustered_sql_fragment(super, opts)
513
+ end
514
+
495
515
  # MSSQL supports views with check option, but not local.
496
516
  def view_with_check_option_support
497
517
  true
@@ -184,7 +184,7 @@ module Sequel
184
184
 
185
185
  def create_table_from_generator(name, generator, options)
186
186
  drop_statement, create_statements = create_table_sql_list(name, generator, options)
187
- (execute_ddl(drop_statement) rescue nil) if drop_statement
187
+ swallow_database_error{execute_ddl(drop_statement)} if drop_statement
188
188
  create_statements.each{|sql| execute_ddl(sql)}
189
189
  end
190
190
 
@@ -781,7 +781,7 @@ module Sequel
781
781
  return @server_version if @server_version
782
782
  ds = dataset
783
783
  ds = ds.server(server) if server
784
- @server_version ||= ds.with_sql("SELECT CAST(current_setting('server_version_num') AS integer) AS v").single_value rescue 0
784
+ @server_version = swallow_database_error{ds.with_sql("SELECT CAST(current_setting('server_version_num') AS integer) AS v").single_value} || 0
785
785
  end
786
786
 
787
787
  # PostgreSQL supports CREATE TABLE IF NOT EXISTS on 9.1+
@@ -846,7 +846,7 @@ module Sequel
846
846
  # :schema :: The schema to search
847
847
  # :server :: The server to use
848
848
  def tables(opts=OPTS, &block)
849
- pg_class_relname('r', opts, &block)
849
+ pg_class_relname(['r', 'p'], opts, &block)
850
850
  end
851
851
 
852
852
  # Check whether the given type name string/symbol (e.g. :hstore) is supported by
@@ -1500,9 +1500,11 @@ module Sequel
1500
1500
  # disallowed or there is a size specified, use the varchar type.
1501
1501
  # Otherwise use the text type.
1502
1502
  def type_literal_generic_string(column)
1503
- if column[:fixed]
1503
+ if column[:text]
1504
+ :text
1505
+ elsif column[:fixed]
1504
1506
  "char(#{column[:size]||255})"
1505
- elsif column[:text] == false or column[:size]
1507
+ elsif column[:text] == false || column[:size]
1506
1508
  "varchar(#{column[:size]||255})"
1507
1509
  else
1508
1510
  :text
@@ -561,7 +561,7 @@ module Sequel
561
561
  Dataset.def_sql_method(self, :delete, [['if db.sqlite_version >= 30803', %w'with delete from where'], ["else", %w'delete from where']])
562
562
  Dataset.def_sql_method(self, :insert, [['if db.sqlite_version >= 30803', %w'with insert conflict into columns values on_conflict'], ["else", %w'insert conflict into columns values']])
563
563
  Dataset.def_sql_method(self, :select, [['if opts[:values]', %w'with values compounds'], ['else', %w'with select distinct columns from join where group having window compounds order limit lock']])
564
- Dataset.def_sql_method(self, :update, [['if db.sqlite_version >= 30803', %w'with update table set where'], ["else", %w'update table set where']])
564
+ Dataset.def_sql_method(self, :update, [['if db.sqlite_version >= 33300', %w'with update table set from where'], ['elsif db.sqlite_version >= 30803', %w'with update table set where'], ["else", %w'update table set where']])
565
565
 
566
566
  def cast_sql_append(sql, expr, type)
567
567
  if type == Time or type == DateTime
@@ -753,6 +753,11 @@ module Sequel
753
753
  false
754
754
  end
755
755
 
756
+ # SQLite does not support deleting from a joined dataset
757
+ def supports_deleting_joins?
758
+ false
759
+ end
760
+
756
761
  # SQLite does not support INTERSECT ALL or EXCEPT ALL
757
762
  def supports_intersect_except_all?
758
763
  false
@@ -763,6 +768,11 @@ module Sequel
763
768
  false
764
769
  end
765
770
 
771
+ # SQLite 3.33.0 supports modifying joined datasets
772
+ def supports_modifying_joins?
773
+ db.sqlite_version >= 33300
774
+ end
775
+
766
776
  # SQLite does not support multiple columns for the IN/NOT IN operators
767
777
  def supports_multiple_column_in?
768
778
  false
@@ -825,6 +835,13 @@ module Sequel
825
835
  end
826
836
  end
827
837
 
838
+ # Raise an InvalidOperation exception if insert is not allowed for this dataset.
839
+ def check_insert_allowed!
840
+ raise(InvalidOperation, "Grouped datasets cannot be modified") if opts[:group]
841
+ raise(InvalidOperation, "Joined datasets cannot be modified") if joined_dataset?
842
+ end
843
+ alias check_delete_allowed! check_insert_allowed!
844
+
828
845
  # SQLite supports a maximum of 500 rows in a VALUES clause.
829
846
  def default_import_slice
830
847
  500
@@ -944,6 +961,23 @@ module Sequel
944
961
  def _truncate_sql(table)
945
962
  "DELETE FROM #{table}"
946
963
  end
964
+
965
+ # Use FROM to specify additional tables in an update query
966
+ def update_from_sql(sql)
967
+ if(from = @opts[:from][1..-1]).empty?
968
+ raise(Error, 'Need multiple FROM tables if updating/deleting a dataset with JOINs') if @opts[:join]
969
+ else
970
+ sql << ' FROM '
971
+ source_list_append(sql, from)
972
+ select_join_sql(sql)
973
+ end
974
+ end
975
+
976
+ # Only include the primary table in the main update clause
977
+ def update_table_sql(sql)
978
+ sql << ' '
979
+ source_list_append(sql, @opts[:from][0..0])
980
+ end
947
981
  end
948
982
  end
949
983
  end
data/lib/sequel/core.rb CHANGED
@@ -52,13 +52,12 @@ module Sequel
52
52
  #
53
53
  # Sequel.datetime_class = DateTime
54
54
  #
55
- # Note that +Time+ and +DateTime+ objects
56
- # have a different API, and in cases where they implement the same methods,
57
- # they often implement them differently (e.g. + using seconds on +Time+ and
58
- # days on +DateTime+).
55
+ # Note that +Time+ and +DateTime+ objects have a different API, and in
56
+ # cases where they implement the same methods, they often implement them
57
+ # differently (e.g. + using seconds on +Time+ and days on +DateTime+).
59
58
  attr_accessor :datetime_class
60
59
 
61
- # Set whether Sequel is being used in single threaded mode. by default,
60
+ # Set whether Sequel is being used in single threaded mode. By default,
62
61
  # Sequel uses a thread-safe connection pool, which isn't as fast as the
63
62
  # single threaded connection pool, and also has some additional thread
64
63
  # safety checks. If your program will only have one thread,
@@ -67,7 +66,7 @@ module Sequel
67
66
  # Sequel.single_threaded = true
68
67
  attr_accessor :single_threaded
69
68
 
70
- # Alias of original require method, as Sequel.require is does a relative
69
+ # Alias of original require method, as Sequel.require does a relative
71
70
  # require for backwards compatibility.
72
71
  alias orig_require require
73
72
  private :orig_require
@@ -55,7 +55,6 @@ module Sequel
55
55
 
56
56
  begin
57
57
  db = c.new(opts)
58
- db.test_connection if db.send(:typecast_value_boolean, opts.fetch(:test, true))
59
58
  if block_given?
60
59
  return yield(db)
61
60
  end
@@ -166,6 +166,7 @@ module Sequel
166
166
  end
167
167
 
168
168
  initialize_load_extensions(:extensions)
169
+ test_connection if typecast_value_boolean(@opts.fetch(:test, true)) && respond_to?(:connect, true)
169
170
  rescue
170
171
  Sequel.synchronize{::Sequel::DATABASES.delete(self)} if keep_reference
171
172
  raise
@@ -446,6 +447,19 @@ module Sequel
446
447
  end
447
448
  end
448
449
 
450
+ # Swallow database errors, unless they are connect/disconnect errors.
451
+ def swallow_database_error
452
+ yield
453
+ rescue Sequel::DatabaseDisconnectError, DatabaseConnectionError
454
+ # Always raise disconnect errors
455
+ raise
456
+ rescue Sequel::DatabaseError
457
+ # Don't raise other database errors.
458
+ nil
459
+ # else
460
+ # Don't rescue other exceptions, they will be raised normally.
461
+ end
462
+
449
463
  # Typecast the value to an SQL::Blob
450
464
  def typecast_value_blob(value)
451
465
  value.is_a?(Sequel::SQL::Blob) ? value : Sequel::SQL::Blob.new(value)
@@ -143,8 +143,14 @@ module Sequel
143
143
  # :identity :: Create an identity column.
144
144
  #
145
145
  # MySQL specific options:
146
+ #
146
147
  # :generated_type :: Set the type of column when using :generated_always_as,
147
148
  # should be :virtual or :stored to force a type.
149
+ #
150
+ # Microsoft SQL Server specific options:
151
+ #
152
+ # :clustered :: When using :primary_key or :unique, marks the primary key or unique
153
+ # constraint as CLUSTERED (if true), or NONCLUSTERED (if false).
148
154
  def column(name, type, opts = OPTS)
149
155
  columns << {:name => name, :type => type}.merge!(opts)
150
156
  if index_opts = opts[:index]
@@ -240,7 +240,7 @@ module Sequel
240
240
  if supports_create_or_replace_view?
241
241
  options = options.merge(:replace=>true)
242
242
  else
243
- drop_view(name) rescue nil
243
+ swallow_database_error{drop_view(name)}
244
244
  end
245
245
 
246
246
  create_view(name, source, options)
@@ -580,14 +580,14 @@ module Sequel
580
580
  sql << ' NULL'
581
581
  end
582
582
  end
583
-
583
+
584
584
  # Add primary key SQL fragment to column creation SQL.
585
585
  def column_definition_primary_key_sql(sql, column)
586
586
  if column[:primary_key]
587
587
  if name = column[:primary_key_constraint_name]
588
588
  sql << " CONSTRAINT #{quote_identifier(name)}"
589
589
  end
590
- sql << ' PRIMARY KEY'
590
+ sql << " " << primary_key_constraint_sql_fragment(column)
591
591
  constraint_deferrable_sql_append(sql, column[:primary_key_deferrable])
592
592
  end
593
593
  end
@@ -608,7 +608,7 @@ module Sequel
608
608
  if name = column[:unique_constraint_name]
609
609
  sql << " CONSTRAINT #{quote_identifier(name)}"
610
610
  end
611
- sql << ' UNIQUE'
611
+ sql << ' ' << unique_constraint_sql_fragment(column)
612
612
  constraint_deferrable_sql_append(sql, column[:unique_deferrable])
613
613
  end
614
614
  end
@@ -656,11 +656,11 @@ module Sequel
656
656
  check = "(#{check})" unless check[0..0] == '(' && check[-1..-1] == ')'
657
657
  sql << "CHECK #{check}"
658
658
  when :primary_key
659
- sql << "PRIMARY KEY #{literal(constraint[:columns])}"
659
+ sql << "#{primary_key_constraint_sql_fragment(constraint)} #{literal(constraint[:columns])}"
660
660
  when :foreign_key
661
661
  sql << column_references_table_constraint_sql(constraint.merge(:deferrable=>nil))
662
662
  when :unique
663
- sql << "UNIQUE #{literal(constraint[:columns])}"
663
+ sql << "#{unique_constraint_sql_fragment(constraint)} #{literal(constraint[:columns])}"
664
664
  else
665
665
  raise Error, "Invalid constraint type #{constraint[:type]}, should be :check, :primary_key, :foreign_key, or :unique"
666
666
  end
@@ -892,6 +892,11 @@ module Sequel
892
892
  on_delete_clause(action)
893
893
  end
894
894
 
895
+ # Add fragment for primary key specification, separated for easier overridding.
896
+ def primary_key_constraint_sql_fragment(_)
897
+ 'PRIMARY KEY'
898
+ end
899
+
895
900
  # Proxy the quote_schema_table method to the dataset
896
901
  def quote_schema_table(table)
897
902
  schema_utility_dataset.quote_schema_table(table)
@@ -1047,6 +1052,11 @@ module Sequel
1047
1052
  "#{type}#{literal(Array(elements)) if elements}#{' UNSIGNED' if column[:unsigned]}"
1048
1053
  end
1049
1054
 
1055
+ # Add fragment for unique specification, separated for easier overridding.
1056
+ def unique_constraint_sql_fragment(_)
1057
+ 'UNIQUE'
1058
+ end
1059
+
1050
1060
  # Whether clob should be used for String text: true columns.
1051
1061
  def uses_clob_for_text?
1052
1062
  false
@@ -82,7 +82,7 @@ module Sequel
82
82
  # :server :: The server/shard the transaction is being executed on.
83
83
  def rollback_on_exit(opts=OPTS)
84
84
  synchronize(opts[:server]) do |conn|
85
- raise Error, "Cannot call Sequel:: Database#rollback_on_exit! unless inside a transaction" unless h = _trans(conn)
85
+ raise Error, "Cannot call Sequel:: Database#rollback_on_exit unless inside a transaction" unless h = _trans(conn)
86
86
  rollback = !opts[:cancel]
87
87
 
88
88
  if supports_savepoints?
@@ -607,14 +607,16 @@ module Sequel
607
607
  # as_hash, it accepts an optional :hash parameter, into which entries will
608
608
  # be merged.
609
609
  #
610
- # DB[:table].select_hash(:id, :name) # SELECT id, name FROM table
610
+ # DB[:table].select_hash(:id, :name)
611
+ # # SELECT id, name FROM table
611
612
  # # => {1=>'a', 2=>'b', ...}
612
613
  #
613
614
  # You can also provide an array of column names for either the key_column,
614
615
  # the value column, or both:
615
616
  #
616
- # DB[:table].select_hash([:id, :foo], [:name, :bar]) # SELECT * FROM table
617
- # # {[1, 3]=>['a', 'c'], [2, 4]=>['b', 'd'], ...}
617
+ # DB[:table].select_hash([:id, :foo], [:name, :bar])
618
+ # # SELECT id, foo, name, bar FROM table
619
+ # # => {[1, 3]=>['a', 'c'], [2, 4]=>['b', 'd'], ...}
618
620
  #
619
621
  # When using this method, you must be sure that each expression has an alias
620
622
  # that Sequel can determine.
@@ -626,14 +628,16 @@ module Sequel
626
628
  # Similar to to_hash_groups, but only selects the columns given. Like to_hash_groups,
627
629
  # it accepts an optional :hash parameter, into which entries will be merged.
628
630
  #
629
- # DB[:table].select_hash_groups(:name, :id) # SELECT id, name FROM table
631
+ # DB[:table].select_hash_groups(:name, :id)
632
+ # # SELECT id, name FROM table
630
633
  # # => {'a'=>[1, 4, ...], 'b'=>[2, ...], ...}
631
634
  #
632
635
  # You can also provide an array of column names for either the key_column,
633
636
  # the value column, or both:
634
637
  #
635
- # DB[:table].select_hash_groups([:first, :middle], [:last, :id]) # SELECT * FROM table
636
- # # {['a', 'b']=>[['c', 1], ['d', 2], ...], ...}
638
+ # DB[:table].select_hash_groups([:first, :middle], [:last, :id])
639
+ # # SELECT first, middle, last, id FROM table
640
+ # # => {['a', 'b']=>[['c', 1], ['d', 2], ...], ...}
637
641
  #
638
642
  # When using this method, you must be sure that each expression has an alias
639
643
  # that Sequel can determine.
@@ -51,6 +51,11 @@ module Sequel
51
51
  false
52
52
  end
53
53
 
54
+ # Whether deleting from joined datasets is supported, false by default.
55
+ def supports_deleting_joins?
56
+ supports_modifying_joins?
57
+ end
58
+
54
59
  # Whether the database supports derived column lists (e.g.
55
60
  # "table_expr AS table_alias(column_alias1, column_alias2, ...)"), true by
56
61
  # default.
@@ -178,6 +183,11 @@ module Sequel
178
183
  true
179
184
  end
180
185
 
186
+ # Whether updating joined datasets is supported, false by default.
187
+ def supports_updating_joins?
188
+ supports_modifying_joins?
189
+ end
190
+
181
191
  # Whether the dataset supports the WINDOW clause to define windows used by multiple
182
192
  # window functions, false by default.
183
193
  def supports_window_clause?
@@ -201,7 +201,9 @@ module Sequel
201
201
  when :insert_pk
202
202
  fetch_rows(prepared_sql){|r| return r.values.first}
203
203
  when Array
204
+ # :nocov:
204
205
  case prepared_type[0]
206
+ # :nocov:
205
207
  when :map, :as_hash, :to_hash, :to_hash_groups
206
208
  public_send(*prepared_type, &block)
207
209
  end