sequel 5.57.0 → 5.60.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +34 -0
  3. data/README.rdoc +25 -0
  4. data/bin/sequel +11 -3
  5. data/doc/cheat_sheet.rdoc +8 -0
  6. data/doc/opening_databases.rdoc +10 -6
  7. data/doc/release_notes/5.58.0.txt +31 -0
  8. data/doc/release_notes/5.59.0.txt +73 -0
  9. data/doc/release_notes/5.60.0.txt +22 -0
  10. data/doc/testing.rdoc +1 -1
  11. data/lib/sequel/adapters/jdbc/derby.rb +5 -0
  12. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  13. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  14. data/lib/sequel/adapters/jdbc/sqlite.rb +1 -1
  15. data/lib/sequel/adapters/jdbc.rb +5 -5
  16. data/lib/sequel/adapters/mock.rb +1 -1
  17. data/lib/sequel/adapters/mysql.rb +3 -3
  18. data/lib/sequel/adapters/oracle.rb +1 -1
  19. data/lib/sequel/adapters/postgres.rb +58 -17
  20. data/lib/sequel/adapters/shared/db2.rb +28 -0
  21. data/lib/sequel/adapters/shared/mssql.rb +35 -1
  22. data/lib/sequel/adapters/shared/mysql.rb +6 -0
  23. data/lib/sequel/adapters/shared/oracle.rb +70 -1
  24. data/lib/sequel/adapters/shared/postgres.rb +94 -18
  25. data/lib/sequel/adapters/shared/sqlite.rb +1 -1
  26. data/lib/sequel/adapters/sqlite.rb +1 -1
  27. data/lib/sequel/ast_transformer.rb +1 -1
  28. data/lib/sequel/database/misc.rb +2 -2
  29. data/lib/sequel/database/schema_generator.rb +1 -0
  30. data/lib/sequel/database/schema_methods.rb +3 -0
  31. data/lib/sequel/dataset/actions.rb +49 -0
  32. data/lib/sequel/dataset/features.rb +5 -0
  33. data/lib/sequel/dataset/query.rb +62 -0
  34. data/lib/sequel/dataset/sql.rb +114 -27
  35. data/lib/sequel/extensions/date_arithmetic.rb +35 -7
  36. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  37. data/lib/sequel/extensions/is_distinct_from.rb +3 -1
  38. data/lib/sequel/extensions/pg_array.rb +2 -2
  39. data/lib/sequel/extensions/pg_array_ops.rb +1 -1
  40. data/lib/sequel/extensions/pg_enum.rb +1 -1
  41. data/lib/sequel/extensions/pg_hstore.rb +1 -1
  42. data/lib/sequel/extensions/pg_hstore_ops.rb +3 -3
  43. data/lib/sequel/extensions/pg_inet.rb +2 -2
  44. data/lib/sequel/extensions/pg_interval.rb +1 -1
  45. data/lib/sequel/extensions/pg_json.rb +1 -1
  46. data/lib/sequel/extensions/pg_json_ops.rb +55 -3
  47. data/lib/sequel/extensions/pg_multirange.rb +2 -2
  48. data/lib/sequel/extensions/pg_range.rb +2 -2
  49. data/lib/sequel/extensions/pg_row.rb +2 -2
  50. data/lib/sequel/extensions/pg_static_cache_updater.rb +2 -2
  51. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  52. data/lib/sequel/model/associations.rb +18 -6
  53. data/lib/sequel/model/base.rb +17 -7
  54. data/lib/sequel/model/exceptions.rb +1 -1
  55. data/lib/sequel/model/inflections.rb +6 -6
  56. data/lib/sequel/plugins/auto_validations.rb +1 -1
  57. data/lib/sequel/plugins/defaults_setter.rb +1 -1
  58. data/lib/sequel/plugins/dirty.rb +1 -1
  59. data/lib/sequel/plugins/insert_conflict.rb +1 -1
  60. data/lib/sequel/plugins/json_serializer.rb +1 -1
  61. data/lib/sequel/plugins/list.rb +3 -1
  62. data/lib/sequel/plugins/nested_attributes.rb +1 -1
  63. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +1 -1
  64. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  65. data/lib/sequel/plugins/serialization.rb +1 -1
  66. data/lib/sequel/plugins/sharding.rb +1 -1
  67. data/lib/sequel/plugins/sql_comments.rb +4 -4
  68. data/lib/sequel/plugins/subclasses.rb +1 -1
  69. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -0
  70. data/lib/sequel/plugins/validation_class_methods.rb +3 -3
  71. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  72. data/lib/sequel/sql.rb +1 -1
  73. data/lib/sequel/version.rb +1 -1
  74. metadata +9 -16
@@ -230,7 +230,6 @@ module Sequel
230
230
  module DatabaseMethods
231
231
  include UnmodifiedIdentifiers::DatabaseMethods
232
232
 
233
- PREPARED_ARG_PLACEHOLDER = LiteralString.new('$').freeze
234
233
  FOREIGN_KEY_LIST_ON_DELETE_MAP = {'a'=>:no_action, 'r'=>:restrict, 'c'=>:cascade, 'n'=>:set_null, 'd'=>:set_default}.freeze
235
234
  ON_COMMIT = {:drop => 'DROP', :delete_rows => 'DELETE ROWS', :preserve_rows => 'PRESERVE ROWS'}.freeze
236
235
  ON_COMMIT.each_value(&:freeze)
@@ -365,9 +364,10 @@ module Sequel
365
364
 
366
365
  table_oid = regclass_oid(table)
367
366
  im = input_identifier_meth
368
- unless column = im.call(opts[:column] || ((sch = schema(table).find{|_, sc| sc[:primary_key] && sc[:auto_increment]}) && sch[0]))
367
+ unless column = (opts[:column] || ((sch = schema(table).find{|_, sc| sc[:primary_key] && sc[:auto_increment]}) && sch[0]))
369
368
  raise Error, "could not determine column to convert from serial to identity automatically"
370
369
  end
370
+ column = im.call(column)
371
371
 
372
372
  column_num = ds.from(:pg_attribute).
373
373
  where(:attrelid=>table_oid, :attname=>column).
@@ -569,10 +569,12 @@ module Sequel
569
569
  if server_version >= 90500
570
570
  cpos = Sequel.expr{array_position(co[:conkey], ctable[:attnum])}
571
571
  rpos = Sequel.expr{array_position(co[:confkey], rtable[:attnum])}
572
+ # :nocov:
572
573
  else
573
574
  range = 0...32
574
575
  cpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:conkey], [x]), x]}, 32, ctable[:attnum])}
575
576
  rpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:confkey], [x]), x]}, 32, rtable[:attnum])}
577
+ # :nocov:
576
578
  end
577
579
 
578
580
  ds = metadata_dataset.
@@ -653,9 +655,11 @@ module Sequel
653
655
 
654
656
  if server_version >= 90500
655
657
  order = [Sequel[:indc][:relname], Sequel.function(:array_position, Sequel[:ind][:indkey], Sequel[:att][:attnum])]
658
+ # :nocov:
656
659
  else
657
660
  range = 0...32
658
661
  order = [Sequel[:indc][:relname], SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(Sequel[:ind][:indkey], [x]), x]}, 32, Sequel[:att][:attnum])]
662
+ # :nocov:
659
663
  end
660
664
 
661
665
  attnums = SQL::Function.new(:ANY, Sequel[:ind][:indkey])
@@ -676,8 +680,10 @@ module Sequel
676
680
  select{[indc[:relname].as(:name), ind[:indisunique].as(:unique), att[:attname].as(:column), con[:condeferrable].as(:deferrable)]}
677
681
 
678
682
  ds = ds.where(:indpred=>nil) unless opts[:include_partial]
683
+ # :nocov:
679
684
  ds = ds.where(:indisready=>true) if server_version >= 80300
680
685
  ds = ds.where(:indislive=>true) if server_version >= 90300
686
+ # :nocov:
681
687
 
682
688
  indexes = {}
683
689
  ds.each do |r|
@@ -758,10 +764,12 @@ module Sequel
758
764
  seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq)))
759
765
  increment_by = :seqincrement
760
766
  min_value = :seqmin
767
+ # :nocov:
761
768
  else
762
769
  seq_ds = metadata_dataset.from(LiteralString.new(seq))
763
770
  increment_by = :increment_by
764
771
  min_value = :min_value
772
+ # :nocov:
765
773
  end
766
774
 
767
775
  get{setval(seq, db[table].select(coalesce(max(pk)+seq_ds.select(increment_by), seq_ds.select(min_value))), false)}
@@ -774,7 +782,9 @@ module Sequel
774
782
  # PostgreSQL uses SERIAL psuedo-type instead of AUTOINCREMENT for
775
783
  # managing incrementing primary keys.
776
784
  def serial_primary_key_options
785
+ # :nocov:
777
786
  auto_increment_key = server_version >= 100002 ? :identity : :serial
787
+ # :nocov:
778
788
  {:primary_key => true, auto_increment_key => true, :type=>Integer}
779
789
  end
780
790
 
@@ -1152,7 +1162,7 @@ module Sequel
1152
1162
  when :hash
1153
1163
  mod, remainder = generator.hash_values
1154
1164
  sql << " FOR VALUES WITH (MODULUS #{literal(mod)}, REMAINDER #{literal(remainder)})"
1155
- when :default
1165
+ else # when :default
1156
1166
  sql << " DEFAULT"
1157
1167
  end
1158
1168
 
@@ -1247,6 +1257,10 @@ module Sequel
1247
1257
  def create_view_prefix_sql(name, options)
1248
1258
  sql = create_view_sql_append_columns("CREATE #{'OR REPLACE 'if options[:replace]}#{'TEMPORARY 'if options[:temp]}#{'RECURSIVE ' if options[:recursive]}#{'MATERIALIZED ' if options[:materialized]}VIEW #{quote_schema_table(name)}", options[:columns] || options[:recursive])
1249
1259
 
1260
+ if options[:security_invoker]
1261
+ sql += " WITH (security_invoker)"
1262
+ end
1263
+
1250
1264
  if tablespace = options[:tablespace]
1251
1265
  sql += " TABLESPACE #{quote_identifier(tablespace)}"
1252
1266
  end
@@ -1304,16 +1318,20 @@ module Sequel
1304
1318
  def index_definition_sql(table_name, index)
1305
1319
  cols = index[:columns]
1306
1320
  index_name = index[:name] || default_index_name(table_name, cols)
1321
+
1307
1322
  expr = if o = index[:opclass]
1308
1323
  "(#{Array(cols).map{|c| "#{literal(c)} #{o}"}.join(', ')})"
1309
1324
  else
1310
1325
  literal(Array(cols))
1311
1326
  end
1327
+
1312
1328
  if_not_exists = " IF NOT EXISTS" if index[:if_not_exists]
1313
1329
  unique = "UNIQUE " if index[:unique]
1314
1330
  index_type = index[:type]
1315
1331
  filter = index[:where] || index[:filter]
1316
1332
  filter = " WHERE #{filter_expr(filter)}" if filter
1333
+ nulls_distinct = " NULLS#{' NOT' if index[:nulls_distinct] == false} DISTINCT" unless index[:nulls_distinct].nil?
1334
+
1317
1335
  case index_type
1318
1336
  when :full_text
1319
1337
  expr = "(to_tsvector(#{literal(index[:language] || 'simple')}::regconfig, #{literal(dataset.send(:full_text_string_join, cols))}))"
@@ -1321,7 +1339,8 @@ module Sequel
1321
1339
  when :spatial
1322
1340
  index_type = :gist
1323
1341
  end
1324
- "CREATE #{unique}INDEX#{' CONCURRENTLY' if index[:concurrently]}#{if_not_exists} #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{" INCLUDE #{literal(Array(index[:include]))}" if index[:include]}#{" TABLESPACE #{quote_identifier(index[:tablespace])}" if index[:tablespace]}#{filter}"
1342
+
1343
+ "CREATE #{unique}INDEX#{' CONCURRENTLY' if index[:concurrently]}#{if_not_exists} #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{" INCLUDE #{literal(Array(index[:include]))}" if index[:include]}#{nulls_distinct}#{" TABLESPACE #{quote_identifier(index[:tablespace])}" if index[:tablespace]}#{filter}"
1325
1344
  end
1326
1345
 
1327
1346
  # Setup datastructures shared by all postgres adapters.
@@ -1347,11 +1366,6 @@ module Sequel
1347
1366
  end
1348
1367
  end
1349
1368
 
1350
- # Use a dollar sign instead of question mark for the argument placeholder.
1351
- def prepared_arg_placeholder
1352
- PREPARED_ARG_PLACEHOLDER
1353
- end
1354
-
1355
1369
  # Return an expression the oid for the table expr. Used by the metadata parsing
1356
1370
  # code to disambiguate unqualified tables.
1357
1371
  def regclass_oid(expr, opts=OPTS)
@@ -1425,10 +1439,14 @@ module Sequel
1425
1439
  where{{pg_class[:oid]=>oid}}.
1426
1440
  order{pg_attribute[:attnum]}
1427
1441
 
1442
+ # :nocov:
1428
1443
  if server_version > 100000
1444
+ # :nocov:
1429
1445
  ds = ds.select_append{pg_attribute[:attidentity]}
1430
1446
 
1447
+ # :nocov:
1431
1448
  if server_version > 120000
1449
+ # :nocov:
1432
1450
  ds = ds.select_append{Sequel.~(pg_attribute[:attgenerated]=>'').as(:generated)}
1433
1451
  end
1434
1452
  end
@@ -1516,7 +1534,9 @@ module Sequel
1516
1534
 
1517
1535
  # PostgreSQL 9.4+ supports views with check option.
1518
1536
  def view_with_check_option_support
1537
+ # :nocov:
1519
1538
  :local if server_version >= 90400
1539
+ # :nocov:
1520
1540
  end
1521
1541
  end
1522
1542
 
@@ -1527,7 +1547,7 @@ module Sequel
1527
1547
  LOCK_MODES = ['ACCESS SHARE', 'ROW SHARE', 'ROW EXCLUSIVE', 'SHARE UPDATE EXCLUSIVE', 'SHARE', 'SHARE ROW EXCLUSIVE', 'EXCLUSIVE', 'ACCESS EXCLUSIVE'].each(&:freeze).freeze
1528
1548
 
1529
1549
  Dataset.def_sql_method(self, :delete, [['if server_version >= 90100', %w'with delete from using where returning'], ['else', %w'delete from using where returning']])
1530
- Dataset.def_sql_method(self, :insert, [['if server_version >= 90500', %w'with insert into columns values conflict returning'], ['elsif server_version >= 90100', %w'with insert into columns values returning'], ['else', %w'insert into columns values returning']])
1550
+ Dataset.def_sql_method(self, :insert, [['if server_version >= 90500', %w'with insert into columns override values conflict returning'], ['elsif server_version >= 90100', %w'with insert into columns values returning'], ['else', %w'insert into columns values returning']])
1531
1551
  Dataset.def_sql_method(self, :select, [['if opts[:values]', %w'values order limit'], ['elsif server_version >= 80400', %w'with select distinct columns from join where group having window compounds order limit lock'], ['else', %w'select distinct columns from join where group having compounds order limit lock']])
1532
1552
  Dataset.def_sql_method(self, :update, [['if server_version >= 90100', %w'with update table set from where returning'], ['else', %w'update table set from where returning']])
1533
1553
 
@@ -1760,6 +1780,41 @@ module Sequel
1760
1780
  nil
1761
1781
  end
1762
1782
 
1783
+ # Return a dataset with a WHEN MATCHED THEN DO NOTHING clause added to the
1784
+ # MERGE statement. If a block is passed, treat it as a virtual row and
1785
+ # use it as additional conditions for the match.
1786
+ #
1787
+ # merge_do_nothing_when_matched
1788
+ # # WHEN MATCHED THEN DO NOTHING
1789
+ #
1790
+ # merge_do_nothing_when_matched{a > 30}
1791
+ # # WHEN MATCHED AND (a > 30) THEN DO NOTHING
1792
+ def merge_do_nothing_when_matched(&block)
1793
+ _merge_when(:type=>:matched, &block)
1794
+ end
1795
+
1796
+ # Return a dataset with a WHEN NOT MATCHED THEN DO NOTHING clause added to the
1797
+ # MERGE statement. If a block is passed, treat it as a virtual row and
1798
+ # use it as additional conditions for the match.
1799
+ #
1800
+ # merge_do_nothing_when_not_matched
1801
+ # # WHEN NOT MATCHED THEN DO NOTHING
1802
+ #
1803
+ # merge_do_nothing_when_not_matched{a > 30}
1804
+ # # WHEN NOT MATCHED AND (a > 30) THEN DO NOTHING
1805
+ def merge_do_nothing_when_not_matched(&block)
1806
+ _merge_when(:type=>:not_matched, &block)
1807
+ end
1808
+
1809
+ # Support OVERRIDING USER|SYSTEM VALUE for MERGE INSERT.
1810
+ def merge_insert(*values, &block)
1811
+ h = {:type=>:insert, :values=>values}
1812
+ if override = @opts[:override]
1813
+ h[:override] = insert_override_sql(String.new)
1814
+ end
1815
+ _merge_when(h, &block)
1816
+ end
1817
+
1763
1818
  # Use OVERRIDING USER VALUE for INSERT statements, so that identity columns
1764
1819
  # always use the user supplied value, and an error is not raised for identity
1765
1820
  # columns that are GENERATED ALWAYS.
@@ -1827,6 +1882,11 @@ module Sequel
1827
1882
  true
1828
1883
  end
1829
1884
 
1885
+ # PostgreSQL 15+ supports MERGE.
1886
+ def supports_merge?
1887
+ server_version >= 150000
1888
+ end
1889
+
1830
1890
  # PostgreSQL supports NOWAIT.
1831
1891
  def supports_nowait?
1832
1892
  true
@@ -1872,6 +1932,8 @@ module Sequel
1872
1932
  server_version >= 90000
1873
1933
  when :groups, :exclude
1874
1934
  server_version >= 110000
1935
+ else
1936
+ false
1875
1937
  end
1876
1938
  end
1877
1939
 
@@ -1937,6 +1999,22 @@ module Sequel
1937
1999
 
1938
2000
  private
1939
2001
 
2002
+ # Append the INSERT sql used in a MERGE
2003
+ def _merge_insert_sql(sql, data)
2004
+ sql << " THEN INSERT "
2005
+ columns, values = _parse_insert_sql_args(data[:values])
2006
+ _insert_columns_sql(sql, columns)
2007
+ if override = data[:override]
2008
+ sql << override
2009
+ end
2010
+ _insert_values_sql(sql, values)
2011
+ end
2012
+
2013
+ def _merge_matched_sql(sql, data)
2014
+ sql << " THEN DO NOTHING"
2015
+ end
2016
+ alias _merge_not_matched_sql _merge_matched_sql
2017
+
1940
2018
  # Format TRUNCATE statement with PostgreSQL specific options.
1941
2019
  def _truncate_sql(table)
1942
2020
  to = @opts[:truncate_opts] || OPTS
@@ -2002,25 +2080,23 @@ module Sequel
2002
2080
 
2003
2081
  # Return the primary key to use for RETURNING in an INSERT statement
2004
2082
  def insert_pk
2005
- if (f = opts[:from]) && !f.empty?
2006
- case t = f.first
2007
- when Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier
2008
- if pk = db.primary_key(t)
2009
- Sequel::SQL::Identifier.new(pk)
2010
- end
2083
+ (f = opts[:from]) && !f.empty? && (t = f.first)
2084
+ case t
2085
+ when Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier
2086
+ if pk = db.primary_key(t)
2087
+ Sequel::SQL::Identifier.new(pk)
2011
2088
  end
2012
2089
  end
2013
2090
  end
2014
2091
 
2015
2092
  # Support OVERRIDING SYSTEM|USER VALUE in insert statements
2016
- def insert_values_sql(sql)
2093
+ def insert_override_sql(sql)
2017
2094
  case opts[:override]
2018
2095
  when :system
2019
2096
  sql << " OVERRIDING SYSTEM VALUE"
2020
2097
  when :user
2021
2098
  sql << " OVERRIDING USER VALUE"
2022
2099
  end
2023
- super
2024
2100
  end
2025
2101
 
2026
2102
  # For multiple table support, PostgreSQL requires at least
@@ -663,7 +663,7 @@ module Sequel
663
663
 
664
664
  # HAVING requires GROUP BY on SQLite
665
665
  def having(*cond)
666
- raise(InvalidOperation, "Can only specify a HAVING clause on a grouped dataset") unless @opts[:group]
666
+ raise(InvalidOperation, "Can only specify a HAVING clause on a grouped dataset") if !@opts[:group] && db.sqlite_version < 33900
667
667
  super
668
668
  end
669
669
 
@@ -305,7 +305,7 @@ module Sequel
305
305
  if USE_EXTENDED_RESULT_CODES
306
306
  # Support SQLite exception codes if ruby-sqlite3 supports them.
307
307
  def sqlite_error_code(exception)
308
- exception.code if exception.respond_to?(:code)
308
+ exception.code if defined?(exception.code)
309
309
  end
310
310
  end
311
311
  end
@@ -81,7 +81,7 @@ module Sequel
81
81
  when SQL::Wrapper
82
82
  SQL::Wrapper.new(v(o.value))
83
83
  when SQL::Expression
84
- if o.respond_to?(:sequel_ast_transform)
84
+ if defined?(o.sequel_ast_transform)
85
85
  o.sequel_ast_transform(method(:v))
86
86
  else
87
87
  o
@@ -350,7 +350,7 @@ module Sequel
350
350
  # strings with all whitespace, and ones that respond
351
351
  # true to empty?
352
352
  def blank_object?(obj)
353
- return obj.blank? if obj.respond_to?(:blank?)
353
+ return obj.blank? if defined?(obj.blank?)
354
354
  case obj
355
355
  when NilClass, FalseClass
356
356
  true
@@ -359,7 +359,7 @@ module Sequel
359
359
  when String
360
360
  obj.strip.empty?
361
361
  else
362
- obj.respond_to?(:empty?) ? obj.empty? : false
362
+ defined?(obj.empty?) ? obj.empty? : false
363
363
  end
364
364
  end
365
365
 
@@ -262,6 +262,7 @@ module Sequel
262
262
  # operations on the table while the index is being
263
263
  # built.
264
264
  # :if_not_exists :: Only create the index if an index of the same name doesn't already exist.
265
+ # :nulls_distinct :: Set whether separate NULLs should be considered distinct values in unique indexes.
265
266
  # :opclass :: Set an opclass to use for all columns (per-column opclasses require
266
267
  # custom SQL).
267
268
  # :tablespace :: Specify tablespace for index.
@@ -295,6 +295,9 @@ module Sequel
295
295
  # in a subquery, if you are providing a Dataset as the source
296
296
  # argument, if should probably call the union method with the
297
297
  # all: true and from_self: false options.
298
+ # :security_invoker :: Set the security_invoker property on the view, making
299
+ # the access to the view use the current user's permissions,
300
+ # instead of the view owner's permissions.
298
301
  # :tablespace :: The tablespace to use for materialized views.
299
302
  def create_view(name, source, options = OPTS)
300
303
  execute_ddl(create_view_sql(name, source, options))
@@ -457,6 +457,55 @@ module Sequel
457
457
  _aggregate(:max, arg)
458
458
  end
459
459
 
460
+ # Execute a MERGE statement, which allows for INSERT, UPDATE, and DELETE
461
+ # behavior in a single query, based on whether rows from a source table
462
+ # match rows in the current table, based on the join conditions.
463
+ #
464
+ # Unless the dataset uses static SQL, to use #merge, you must first have
465
+ # called #merge_using to specify the merge source and join conditions.
466
+ # You will then likely to call one or more of the following methods
467
+ # to specify MERGE behavior by adding WHEN [NOT] MATCHED clauses:
468
+ #
469
+ # * #merge_insert
470
+ # * #merge_update
471
+ # * #merge_delete
472
+ #
473
+ # The WHEN [NOT] MATCHED clauses are added to the SQL in the order these
474
+ # methods were called on the dataset. If none of these methods are
475
+ # called, an error is raised.
476
+ #
477
+ # Example:
478
+ #
479
+ # DB[:m1]
480
+ # merge_using(:m2, i1: :i2).
481
+ # merge_insert(i1: :i2, a: Sequel[:b]+11).
482
+ # merge_delete{a > 30}.
483
+ # merge_update(i1: Sequel[:i1]+:i2+10, a: Sequel[:a]+:b+20).
484
+ # merge
485
+ #
486
+ # SQL:
487
+ #
488
+ # MERGE INTO m1 USING m2 ON (i1 = i2)
489
+ # WHEN NOT MATCHED THEN INSERT (i1, a) VALUES (i2, (b + 11))
490
+ # WHEN MATCHED AND (a > 30) THEN DELETE
491
+ # WHEN MATCHED THEN UPDATE SET i1 = (i1 + i2 + 10), a = (a + b + 20)
492
+ #
493
+ # On PostgreSQL, two additional merge methods are supported, for the
494
+ # PostgreSQL-specific DO NOTHING syntax.
495
+ #
496
+ # * #merge_do_nothing_when_matched
497
+ # * #merge_do_nothing_when_not_matched
498
+ #
499
+ # This method is supported on Oracle, but Oracle's MERGE support is
500
+ # non-standard, and has the following issues:
501
+ #
502
+ # * DELETE clause requires UPDATE clause
503
+ # * DELETE clause requires a condition
504
+ # * DELETE clause only affects rows updated by UPDATE clause
505
+ def merge
506
+ execute_ddl(merge_sql)
507
+ end
508
+
460
509
  # Returns the minimum value for the given column/expression.
461
510
  # Uses a virtual row block if no argument is given.
462
511
  #
@@ -125,6 +125,11 @@ module Sequel
125
125
  false
126
126
  end
127
127
 
128
+ # Whether the MERGE statement is supported, false by default.
129
+ def supports_merge?
130
+ false
131
+ end
132
+
128
133
  # Whether modifying joined datasets is supported, false by default.
129
134
  def supports_modifying_joins?
130
135
  false
@@ -678,6 +678,56 @@ module Sequel
678
678
  clone(:lock => style)
679
679
  end
680
680
 
681
+ # Return a dataset with a WHEN MATCHED THEN DELETE clause added to the
682
+ # MERGE statement. If a block is passed, treat it as a virtual row and
683
+ # use it as additional conditions for the match.
684
+ #
685
+ # merge_delete
686
+ # # WHEN MATCHED THEN DELETE
687
+ #
688
+ # merge_delete{a > 30}
689
+ # # WHEN MATCHED AND (a > 30) THEN DELETE
690
+ def merge_delete(&block)
691
+ _merge_when(:type=>:delete, &block)
692
+ end
693
+
694
+ # Return a dataset with a WHEN NOT MATCHED THEN INSERT clause added to the
695
+ # MERGE statement. If a block is passed, treat it as a virtual row and
696
+ # use it as additional conditions for the match.
697
+ #
698
+ # The arguments provided can be any arguments that would be accepted by
699
+ # #insert.
700
+ #
701
+ # merge_insert(i1: :i2, a: Sequel[:b]+11)
702
+ # # WHEN NOT MATCHED THEN INSERT (i1, a) VALUES (i2, (b + 11))
703
+ #
704
+ # merge_insert(:i2, Sequel[:b]+11){a > 30}
705
+ # # WHEN NOT MATCHED AND (a > 30) THEN INSERT VALUES (i2, (b + 11))
706
+ def merge_insert(*values, &block)
707
+ _merge_when(:type=>:insert, :values=>values, &block)
708
+ end
709
+
710
+ # Return a dataset with a WHEN MATCHED THEN UPDATE clause added to the
711
+ # MERGE statement. If a block is passed, treat it as a virtual row and
712
+ # use it as additional conditions for the match.
713
+ #
714
+ # merge_update(i1: Sequel[:i1]+:i2+10, a: Sequel[:a]+:b+20)
715
+ # # WHEN MATCHED THEN UPDATE SET i1 = (i1 + i2 + 10), a = (a + b + 20)
716
+ #
717
+ # merge_update(i1: :i2){a > 30}
718
+ # # WHEN MATCHED AND (a > 30) THEN UPDATE SET i1 = i2
719
+ def merge_update(values, &block)
720
+ _merge_when(:type=>:update, :values=>values, &block)
721
+ end
722
+
723
+ # Return a dataset with the source and join condition to use for the MERGE statement.
724
+ #
725
+ # merge_using(:m2, i1: :i2)
726
+ # # USING m2 ON (i1 = i2)
727
+ def merge_using(source, join_condition)
728
+ clone(:merge_using => [source, join_condition].freeze)
729
+ end
730
+
681
731
  # Returns a cloned dataset without a row_proc.
682
732
  #
683
733
  # ds = DB[:items].with_row_proc(:invert.to_proc)
@@ -1287,6 +1337,18 @@ module Sequel
1287
1337
  end
1288
1338
  end
1289
1339
 
1340
+ # Append to the current MERGE WHEN clauses.
1341
+ # Mutates the hash to add the conditions, if a virtual row block is passed.
1342
+ def _merge_when(hash, &block)
1343
+ hash[:conditions] = Sequel.virtual_row(&block) if block
1344
+
1345
+ if merge_when = @opts[:merge_when]
1346
+ clone(:merge_when => (merge_when.dup << hash.freeze).freeze)
1347
+ else
1348
+ clone(:merge_when => [hash.freeze].freeze)
1349
+ end
1350
+ end
1351
+
1290
1352
  # Add the given filter condition. Arguments:
1291
1353
  # clause :: Symbol or which SQL clause to effect, should be :where or :having
1292
1354
  # cond :: The filter condition to add
@@ -24,29 +24,7 @@ module Sequel
24
24
 
25
25
  check_insert_allowed!
26
26
 
27
- columns = []
28
-
29
- case values.size
30
- when 0
31
- return insert_sql(OPTS)
32
- when 1
33
- case vals = values[0]
34
- when Hash
35
- values = []
36
- vals.each do |k,v|
37
- columns << k
38
- values << v
39
- end
40
- when Dataset, Array, LiteralString
41
- values = vals
42
- end
43
- when 2
44
- if (v0 = values[0]).is_a?(Array) && ((v1 = values[1]).is_a?(Array) || v1.is_a?(Dataset) || v1.is_a?(LiteralString))
45
- columns, values = v0, v1
46
- raise(Error, "Different number of values and columns given to insert_sql") if values.is_a?(Array) and columns.length != values.length
47
- end
48
- end
49
-
27
+ columns, values = _parse_insert_sql_args(values)
50
28
  if values.is_a?(Array) && values.empty? && !insert_supports_empty_values?
51
29
  columns, values = insert_empty_columns_values
52
30
  elsif values.is_a?(Dataset) && hoist_cte?(values) && supports_cte?(:insert)
@@ -112,6 +90,31 @@ module Sequel
112
90
  end
113
91
  end
114
92
 
93
+ # The SQL to use for the MERGE statement.
94
+ def merge_sql
95
+ raise Error, "This database doesn't support MERGE" unless supports_merge?
96
+ if sql = opts[:sql]
97
+ return static_sql(sql)
98
+ end
99
+ if sql = cache_get(:_merge_sql)
100
+ return sql
101
+ end
102
+ source, join_condition = @opts[:merge_using]
103
+ raise Error, "No USING clause for MERGE" unless source
104
+ sql = @opts[:append_sql] || sql_string_origin
105
+
106
+ select_with_sql(sql)
107
+ sql << "MERGE INTO "
108
+ source_list_append(sql, @opts[:from])
109
+ sql << " USING "
110
+ identifier_append(sql, source)
111
+ sql << " ON "
112
+ literal_append(sql, join_condition)
113
+ _merge_when_sql(sql)
114
+ cache_set(:_merge_sql, sql) if cache_sql?
115
+ sql
116
+ end
117
+
115
118
  # Returns an array of insert statements for inserting multiple records.
116
119
  # This method is used by +multi_insert+ to format insert statements and
117
120
  # expects a keys array and and an array of value arrays.
@@ -850,6 +853,83 @@ module Sequel
850
853
 
851
854
  private
852
855
 
856
+ # Append the INSERT sql used in a MERGE
857
+ def _merge_insert_sql(sql, data)
858
+ sql << " THEN INSERT"
859
+ columns, values = _parse_insert_sql_args(data[:values])
860
+ _insert_columns_sql(sql, columns)
861
+ _insert_values_sql(sql, values)
862
+ end
863
+
864
+ def _merge_update_sql(sql, data)
865
+ sql << " THEN UPDATE SET "
866
+ update_sql_values_hash(sql, data[:values])
867
+ end
868
+
869
+ def _merge_delete_sql(sql, data)
870
+ sql << " THEN DELETE"
871
+ end
872
+
873
+ # Mapping of merge types to related SQL
874
+ MERGE_TYPE_SQL = {
875
+ :insert => ' WHEN NOT MATCHED',
876
+ :delete => ' WHEN MATCHED',
877
+ :update => ' WHEN MATCHED',
878
+ :matched => ' WHEN MATCHED',
879
+ :not_matched => ' WHEN NOT MATCHED',
880
+ }.freeze
881
+ private_constant :MERGE_TYPE_SQL
882
+
883
+ # Add the WHEN clauses to the MERGE SQL
884
+ def _merge_when_sql(sql)
885
+ raise Error, "no WHEN [NOT] MATCHED clauses provided for MERGE" unless merge_when = @opts[:merge_when]
886
+ merge_when.each do |data|
887
+ type = data[:type]
888
+ sql << MERGE_TYPE_SQL[type]
889
+ _merge_when_conditions_sql(sql, data)
890
+ send(:"_merge_#{type}_sql", sql, data)
891
+ end
892
+ end
893
+
894
+ # Append MERGE WHEN conditions, if there are conditions provided.
895
+ def _merge_when_conditions_sql(sql, data)
896
+ if data.has_key?(:conditions)
897
+ sql << " AND "
898
+ literal_append(sql, data[:conditions])
899
+ end
900
+ end
901
+
902
+ # Parse the values passed to insert_sql, returning columns and values
903
+ # to use for the INSERT. Returned columns is always an array, but can be empty
904
+ # for an INSERT without explicit column references. Returned values can be an
905
+ # array, dataset, or literal string.
906
+ def _parse_insert_sql_args(values)
907
+ columns = []
908
+
909
+ case values.size
910
+ when 0
911
+ values = []
912
+ when 1
913
+ case vals = values[0]
914
+ when Hash
915
+ values = []
916
+ vals.each do |k,v|
917
+ columns << k
918
+ values << v
919
+ end
920
+ when Dataset, Array, LiteralString
921
+ values = vals
922
+ end
923
+ when 2
924
+ if (v0 = values[0]).is_a?(Array) && ((v1 = values[1]).is_a?(Array) || v1.is_a?(Dataset) || v1.is_a?(LiteralString))
925
+ columns, values = v0, v1
926
+ raise(Error, "Different number of values and columns given to insert_sql") if values.is_a?(Array) and columns.length != values.length
927
+ end
928
+ end
929
+
930
+ [columns, values]
931
+ end
932
+
853
933
  # Formats the truncate statement. Assumes the table given has already been
854
934
  # literalized.
855
935
  def _truncate_sql(table)
@@ -1165,7 +1245,10 @@ module Sequel
1165
1245
  end
1166
1246
 
1167
1247
  def insert_columns_sql(sql)
1168
- columns = opts[:columns]
1248
+ _insert_columns_sql(sql, opts[:columns])
1249
+ end
1250
+
1251
+ def _insert_columns_sql(sql, columns)
1169
1252
  if columns && !columns.empty?
1170
1253
  sql << ' ('
1171
1254
  identifier_list_append(sql, columns)
@@ -1184,7 +1267,11 @@ module Sequel
1184
1267
  end
1185
1268
 
1186
1269
  def insert_values_sql(sql)
1187
- case values = opts[:values]
1270
+ _insert_values_sql(sql, opts[:values])
1271
+ end
1272
+
1273
+ def _insert_values_sql(sql, values)
1274
+ case values
1188
1275
  when Array
1189
1276
  if values.empty?
1190
1277
  sql << " DEFAULT VALUES"
@@ -1311,9 +1398,9 @@ module Sequel
1311
1398
  # don't cache SQL for a dataset that uses this.
1312
1399
  disable_sql_caching!
1313
1400
 
1314
- if v.respond_to?(:sql_literal_append)
1401
+ if defined?(v.sql_literal_append)
1315
1402
  v.sql_literal_append(self, sql)
1316
- elsif v.respond_to?(:sql_literal)
1403
+ elsif defined?(v.sql_literal)
1317
1404
  sql << v.sql_literal(self)
1318
1405
  else
1319
1406
  raise Error, "can't express #{v.inspect} as a SQL literal"