sequel 5.57.0 → 5.58.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ab346b95d558c843b61291c10b7033a50fa3e68b1b6d76b16c36f35eea7669a
4
- data.tar.gz: eb9acbdab39df252a1e1dd6e66c82a6b13a61544d04d709e46592310ada6f693
3
+ metadata.gz: 2f02f6a35677e8af2ea2b164f655cc869213864e5b27f67cdd3e17c7db28c546
4
+ data.tar.gz: ee09f1b4d5b3f4decbf4d4af3b8ae16ef7e488af40011808dac94f8ca05da89f
5
5
  SHA512:
6
- metadata.gz: 925cdbf7f82dcd9c98fadbaeba92e4b4f36a424e81bde6593a0dc9569d693dc16cc6cb901fe32d0839389348aa3870874589489db4ea4f70eb81e6b2d4e06de4
7
- data.tar.gz: 270e046d9c90956dfb7e81d1e17c5baeba6feb563f8aaf66d86494697e5b59e027375b72e11b24d1eb23709b9e2272c2ed53430c4c4bfc1af2957bfba7e64416
6
+ metadata.gz: 89efe5f1a7f2c2eedba5ac1cf336adee1abf489278f79413756e70a41e4ba073ea818562aaf176c1f092e7ab9e763693473a0d96edc6782d9da037c759afd05d
7
+ data.tar.gz: 84986009ccb30a7a0fd15a555262631f539b95ef9ec8d74d6ca2a4078bd07c33ffda725092bad5afbc93b27901e67dc6d06ca735481448174ac695ef14b6ee99
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ === 5.58.0 (2022-07-01)
2
+
3
+ * Support :disable_split_materialized Database option on MySQL to work around optimizer bug in MariaDB 10.5+ affecting association tests (jeremyevans)
4
+
5
+ * Add Dataset#merge* methods to support MERGE statement on PostgreSQL 15+, MSSQL, Oracle, DB2, H2, HSQLDB, and Derby (jeremyevans)
6
+
1
7
  === 5.57.0 (2022-06-01)
2
8
 
3
9
  * Make Database#create_function on PostgreSQL accept :parallel option (bananarne) (#1870)
data/README.rdoc CHANGED
@@ -414,6 +414,31 @@ As with +delete+, +update+ affects all rows in the dataset, so +where+ first,
414
414
  # NOT THIS:
415
415
  posts.update(:state => 'archived').where(Sequel[:stamp] < Date.today - 7)
416
416
 
417
+ === Merging records
418
+
419
+ Merging records using the SQL MERGE statment is done using <tt>merge*</tt> methods.
420
+ You use +merge_using+ to specify the merge source and join conditions.
421
+ You can use +merge_insert+, +merge_delete+, and/or +merge_update+ to set the
422
+ INSERT, DELETE, and UPDATE clauses for the merge. +merge_insert+ takes the same
423
+ arguments as +insert+, and +merge_update+ takes the same arguments as +update+.
424
+ +merge_insert+, +merge_delete+, and +merge_update+ can all be called with blocks,
425
+ to set the conditions for the related INSERT, DELETE, or UPDATE.
426
+
427
+ Finally, after calling all of the other <tt>merge_*</tt> methods, you call +merge+
428
+ to run the MERGE statement on the database.
429
+
430
+ ds = DB[:m1]
431
+ merge_using(:m2, i1: :i2).
432
+ merge_insert(i1: :i2, a: Sequel[:b]+11).
433
+ merge_delete{a > 30}.
434
+ merge_update(i1: Sequel[:i1]+:i2+10, a: Sequel[:a]+:b+20)
435
+
436
+ ds.merge
437
+ # MERGE INTO m1 USING m2 ON (i1 = i2)
438
+ # WHEN NOT MATCHED THEN INSERT (i1, a) VALUES (i2, (b + 11))
439
+ # WHEN MATCHED AND (a > 30) THEN DELETE
440
+ # WHEN MATCHED THEN UPDATE SET i1 = (i1 + i2 + 10), a = (a + b + 20)
441
+
417
442
  === Transactions
418
443
 
419
444
  You can wrap a block of code in a database transaction using the <tt>Database#transaction</tt> method:
data/doc/cheat_sheet.rdoc CHANGED
@@ -57,6 +57,14 @@ Without a filename argument, the sqlite adapter will setup a new sqlite database
57
57
  dataset.where{price < 100}.update(:active => true)
58
58
  dataset.where(:active).update(:price => Sequel[:price] * 0.90)
59
59
 
60
+ = Merge rows
61
+
62
+ dataset.
63
+ merge_using(:table, col1: :col2).
64
+ merge_insert(col3: :col4).
65
+ merge_delete{col5 > 30}.
66
+ merge_update(col3: Sequel[:col3] + :col4)
67
+
60
68
  == Datasets are Enumerable
61
69
 
62
70
  dataset.map{|r| r[:name]}
@@ -102,6 +102,7 @@ The following options can be specified and are passed to the database's internal
102
102
  :after_connect :: A callable object called after each new connection is made, with the
103
103
  connection object (and server argument if the callable accepts 2 arguments),
104
104
  useful for customizations that you want to apply to all connections (nil by default).
105
+ :connect_sqls :: An array of sql strings to execute on each new connection, after :after_connect runs.
105
106
  :max_connections :: The maximum size of the connection pool (4 connections by default on most databases)
106
107
  :pool_timeout :: The number of seconds to wait if a connection cannot be acquired before raising an error (5 seconds by default)
107
108
  :single_threaded :: Whether to use a single-threaded (non-thread safe) connection pool
@@ -258,6 +259,8 @@ The following additional options are supported:
258
259
  :compress :: Whether to compress data sent/received via the socket connection.
259
260
  :config_default_group :: The default group to read from the in the MySQL config file, defaults to "client")
260
261
  :config_local_infile :: If provided, sets the Mysql::OPT_LOCAL_INFILE option on the connection with the given value.
262
+ :disable_split_materialized :: Set split_materialized=off in the optimizer settings. Necessary to pass the associations
263
+ integration tests in MariaDB 10.5+, due to a unfixed bug in the optimizer.
261
264
  :encoding :: Specify the encoding/character set to use for the connection.
262
265
  :fractional_seconds :: On MySQL 5.6.5+, this option is recognized and will include fractional seconds in
263
266
  time/timestamp values, as well as have the schema method create columns that can contain
@@ -278,7 +281,9 @@ if either the :sslca or :sslkey option is given.
278
281
 
279
282
  This is a newer MySQL adapter that does typecasting in C, so it is often faster than the
280
283
  mysql adapter. The options given are passed to Mysql2::Client.new, see the mysql2 documentation
281
- for details on what options are supported.
284
+ for details on what options are supported. The :timeout, :auto_is_null, :sql_mode, and :disable_split_materialized
285
+ options supported by the mysql adapter are also supported for mysql2 adapter (and any other adapters connecting to
286
+ mysql, such as the jdbc/mysql adapter).
282
287
 
283
288
  === odbc
284
289
 
@@ -0,0 +1,31 @@
1
+ = New Features
2
+
3
+ * Dataset#merge and related #merge_* methods have been added for the
4
+ MERGE statement. MERGE is supported on PostgreSQL 15+, Oracle,
5
+ Microsoft SQL Server, DB2, H2, HSQLDB, and Derby. You can use MERGE
6
+ to insert, update, and/or delete in a single query. You call
7
+ the #merge_* methods to setup the MERGE statement, and #merge to
8
+ execute it on the database:
9
+
10
+ ds = DB[:m1]
11
+ merge_using(:m2, i1: :i2).
12
+ merge_insert(i1: :i2, a: Sequel[:b]+11).
13
+ merge_delete{a > 30}.
14
+ merge_update(i1: Sequel[:i1]+:i2+10, a: Sequel[:a]+:b+20)
15
+
16
+ ds.merge
17
+ # MERGE INTO m1 USING m2 ON (i1 = i2)
18
+ # WHEN NOT MATCHED THEN INSERT (i1, a) VALUES (i2, (b + 11))
19
+ # WHEN MATCHED AND (a > 30) THEN DELETE
20
+ # WHEN MATCHED THEN UPDATE SET i1 = (i1 + i2 + 10), a = (a + b + 20)
21
+
22
+ On PostgreSQL, the following additional MERGE related methods are
23
+ available:
24
+
25
+ * #merge_do_nothing_when_matched
26
+ * #merge_do_nothing_when_not_matched
27
+
28
+ * A :disable_split_materialized Database option is now supported on
29
+ MySQL. This disables split_materialized support in the optimizer,
30
+ working around a bug in MariaDB 10.5+ that causes failures in
31
+ Sequel's association tests.
data/doc/testing.rdoc CHANGED
@@ -113,7 +113,7 @@ The order in which you delete/truncate the tables is important if you are using
113
113
 
114
114
  = Testing Sequel Itself
115
115
 
116
- Sequel has multiple separate test suites. All test suites use minitest/spec, with the minitest-hooks, minitest-global_expectations, and minitest-shared_description extensions. To install the dependencies necessary to test Sequel, run <tt>gem install --development sequel</tt>.
116
+ Sequel has multiple separate test suites. All test suites use minitest/spec, with the minitest-hooks and minitest-global_expectations extensions. To install the dependencies necessary to test Sequel, run <tt>gem install --development sequel</tt>.
117
117
 
118
118
  == rake
119
119
 
@@ -235,6 +235,11 @@ module Sequel
235
235
  false
236
236
  end
237
237
 
238
+ # Derby 10.11+ supports MERGE.
239
+ def supports_merge?
240
+ db.svn_version >= 1616546
241
+ end
242
+
238
243
  # Derby does not support IN/NOT IN with multiple columns
239
244
  def supports_multiple_column_in?
240
245
  false
@@ -229,6 +229,11 @@ module Sequel
229
229
  false
230
230
  end
231
231
 
232
+ # H2 supports MERGE
233
+ def supports_merge?
234
+ true
235
+ end
236
+
232
237
  # H2 doesn't support multiple columns in IN/NOT IN
233
238
  def supports_multiple_column_in?
234
239
  false
@@ -179,6 +179,12 @@ module Sequel
179
179
  true
180
180
  end
181
181
 
182
+ # HSQLDB 2.3.4+ supports MERGE. Older versions also support MERGE, but not all
183
+ # features that are in Sequel's tests.
184
+ def supports_merge?
185
+ db.db_version >= 20304
186
+ end
187
+
182
188
  private
183
189
 
184
190
  def empty_from_sql
@@ -338,6 +338,11 @@ module Sequel
338
338
  true
339
339
  end
340
340
 
341
+ # DB2 supports MERGE
342
+ def supports_merge?
343
+ true
344
+ end
345
+
341
346
  # DB2 does not support multiple columns in IN.
342
347
  def supports_multiple_column_in?
343
348
  false
@@ -360,6 +365,29 @@ module Sequel
360
365
 
361
366
  private
362
367
 
368
+ # Normalize conditions for MERGE WHEN.
369
+ def _merge_when_conditions_sql(sql, data)
370
+ if data.has_key?(:conditions)
371
+ sql << " AND "
372
+ literal_append(sql, _normalize_merge_when_conditions(data[:conditions]))
373
+ end
374
+ end
375
+
376
+ # Handle nil, false, and true MERGE WHEN conditions to avoid non-boolean
377
+ # type error.
378
+ def _normalize_merge_when_conditions(conditions)
379
+ case conditions
380
+ when nil, false
381
+ {1=>0}
382
+ when true
383
+ {1=>1}
384
+ when Sequel::SQL::DelayedEvaluation
385
+ Sequel.delay{_normalize_merge_when_conditions(conditions.call(self))}
386
+ else
387
+ conditions
388
+ end
389
+ end
390
+
363
391
  def empty_from_sql
364
392
  ' FROM "SYSIBM"."SYSDUMMY1"'
365
393
  end
@@ -734,6 +734,11 @@ module Sequel
734
734
  false
735
735
  end
736
736
 
737
+ # MSSQL 2008+ supports MERGE
738
+ def supports_merge?
739
+ is_2008_or_later?
740
+ end
741
+
737
742
  # MSSQL 2005+ supports modifying joined datasets
738
743
  def supports_modifying_joins?
739
744
  is_2005_or_later?
@@ -824,6 +829,35 @@ module Sequel
824
829
 
825
830
  private
826
831
 
832
+ # Normalize conditions for MERGE WHEN.
833
+ def _merge_when_conditions_sql(sql, data)
834
+ if data.has_key?(:conditions)
835
+ sql << " AND "
836
+ literal_append(sql, _normalize_merge_when_conditions(data[:conditions]))
837
+ end
838
+ end
839
+
840
+ # Handle nil, false, and true MERGE WHEN conditions to avoid non-boolean
841
+ # type error.
842
+ def _normalize_merge_when_conditions(conditions)
843
+ case conditions
844
+ when nil, false
845
+ {1=>0}
846
+ when true
847
+ {1=>1}
848
+ when Sequel::SQL::DelayedEvaluation
849
+ Sequel.delay{_normalize_merge_when_conditions(conditions.call(self))}
850
+ else
851
+ conditions
852
+ end
853
+ end
854
+
855
+ # MSSQL requires a semicolon at the end of MERGE.
856
+ def _merge_when_sql(sql)
857
+ super
858
+ sql << ';'
859
+ end
860
+
827
861
  # MSSQL does not allow ordering in sub-clauses unless TOP (limit) is specified
828
862
  def aggregate_dataset
829
863
  (options_overlap(Sequel::Dataset::COUNT_FROM_SELF_OPTS) && !options_overlap([:limit])) ? unordered.from_self : super
@@ -333,6 +333,12 @@ module Sequel
333
333
  sqls << "SET sql_mode = '#{sql_mode}'"
334
334
  end
335
335
 
336
+ # Disable the use of split_materialized in the optimizer. This is
337
+ # needed to pass association tests on MariaDB 10.5+.
338
+ if opts[:disable_split_materialized] && typecast_value_boolean(opts[:disable_split_materialized])
339
+ sqls << "SET SESSION optimizer_switch='split_materialized=off'"
340
+ end
341
+
336
342
  sqls
337
343
  end
338
344
 
@@ -478,6 +478,11 @@ module Sequel
478
478
  false
479
479
  end
480
480
 
481
+ # Oracle supports MERGE
482
+ def supports_merge?
483
+ true
484
+ end
485
+
481
486
  # Oracle supports NOWAIT.
482
487
  def supports_nowait?
483
488
  true
@@ -525,6 +530,70 @@ module Sequel
525
530
 
526
531
  private
527
532
 
533
+ # Handle nil, false, and true MERGE WHEN conditions to avoid non-boolean
534
+ # type error.
535
+ def _normalize_merge_when_conditions(conditions)
536
+ case conditions
537
+ when nil, false
538
+ {1=>0}
539
+ when true
540
+ {1=>1}
541
+ when Sequel::SQL::DelayedEvaluation
542
+ Sequel.delay{_normalize_merge_when_conditions(conditions.call(self))}
543
+ else
544
+ conditions
545
+ end
546
+ end
547
+
548
+ # Handle Oracle's non standard MERGE syntax
549
+ def _merge_when_sql(sql)
550
+ raise Error, "no WHEN [NOT] MATCHED clauses provided for MERGE" unless merge_when = @opts[:merge_when]
551
+ insert = update = delete = nil
552
+ types = merge_when.map{|d| d[:type]}
553
+ raise Error, "Oracle does not support multiple INSERT, UPDATE, or DELETE clauses in MERGE" if types != types.uniq
554
+
555
+ merge_when.each do |data|
556
+ case data[:type]
557
+ when :insert
558
+ insert = data
559
+ when :update
560
+ update = data
561
+ else # when :delete
562
+ delete = data
563
+ end
564
+ end
565
+
566
+ if delete
567
+ raise Error, "Oracle does not support DELETE without UPDATE clause in MERGE" unless update
568
+ raise Error, "Oracle does not support DELETE without conditions clause in MERGE" unless delete.has_key?(:conditions)
569
+ end
570
+
571
+ if update
572
+ sql << " WHEN MATCHED"
573
+ _merge_update_sql(sql, update)
574
+ _merge_when_conditions_sql(sql, update)
575
+
576
+ if delete
577
+ sql << " DELETE"
578
+ _merge_when_conditions_sql(sql, delete)
579
+ end
580
+ end
581
+
582
+ if insert
583
+ sql << " WHEN NOT MATCHED"
584
+ _merge_insert_sql(sql, insert)
585
+ _merge_when_conditions_sql(sql, insert)
586
+ end
587
+ end
588
+
589
+ # Handle Oracle's non-standard MERGE WHEN condition syntax.
590
+ def _merge_when_conditions_sql(sql, data)
591
+ if data.has_key?(:conditions)
592
+ sql << " WHERE "
593
+ literal_append(sql, _normalize_merge_when_conditions(data[:conditions]))
594
+ end
595
+ end
596
+
528
597
  # Allow preparing prepared statements, since determining the prepared sql to use for
529
598
  # a prepared statement requires calling prepare on that statement.
530
599
  def allow_preparing_prepared_statements?
@@ -1527,7 +1527,7 @@ module Sequel
1527
1527
  LOCK_MODES = ['ACCESS SHARE', 'ROW SHARE', 'ROW EXCLUSIVE', 'SHARE UPDATE EXCLUSIVE', 'SHARE', 'SHARE ROW EXCLUSIVE', 'EXCLUSIVE', 'ACCESS EXCLUSIVE'].each(&:freeze).freeze
1528
1528
 
1529
1529
  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']])
1530
+ 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
1531
  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
1532
  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
1533
 
@@ -1760,6 +1760,41 @@ module Sequel
1760
1760
  nil
1761
1761
  end
1762
1762
 
1763
+ # Return a dataset with a WHEN MATCHED THEN DO NOTHING clause added to the
1764
+ # MERGE statement. If a block is passed, treat it as a virtual row and
1765
+ # use it as additional conditions for the match.
1766
+ #
1767
+ # merge_do_nothing_when_matched
1768
+ # # WHEN MATCHED THEN DO NOTHING
1769
+ #
1770
+ # merge_do_nothing_when_matched{a > 30}
1771
+ # # WHEN MATCHED AND (a > 30) THEN DO NOTHING
1772
+ def merge_do_nothing_when_matched(&block)
1773
+ _merge_when(:type=>:matched, &block)
1774
+ end
1775
+
1776
+ # Return a dataset with a WHEN NOT MATCHED THEN DO NOTHING clause added to the
1777
+ # MERGE statement. If a block is passed, treat it as a virtual row and
1778
+ # use it as additional conditions for the match.
1779
+ #
1780
+ # merge_do_nothing_when_not_matched
1781
+ # # WHEN NOT MATCHED THEN DO NOTHING
1782
+ #
1783
+ # merge_do_nothing_when_not_matched{a > 30}
1784
+ # # WHEN NOT MATCHED AND (a > 30) THEN DO NOTHING
1785
+ def merge_do_nothing_when_not_matched(&block)
1786
+ _merge_when(:type=>:not_matched, &block)
1787
+ end
1788
+
1789
+ # Support OVERRIDING USER|SYSTEM VALUE for MERGE INSERT.
1790
+ def merge_insert(*values, &block)
1791
+ h = {:type=>:insert, :values=>values}
1792
+ if override = @opts[:override]
1793
+ h[:override] = insert_override_sql(String.new)
1794
+ end
1795
+ _merge_when(h, &block)
1796
+ end
1797
+
1763
1798
  # Use OVERRIDING USER VALUE for INSERT statements, so that identity columns
1764
1799
  # always use the user supplied value, and an error is not raised for identity
1765
1800
  # columns that are GENERATED ALWAYS.
@@ -1827,6 +1862,11 @@ module Sequel
1827
1862
  true
1828
1863
  end
1829
1864
 
1865
+ # PostgreSQL 15+ supports MERGE.
1866
+ def supports_merge?
1867
+ server_version >= 150000
1868
+ end
1869
+
1830
1870
  # PostgreSQL supports NOWAIT.
1831
1871
  def supports_nowait?
1832
1872
  true
@@ -1937,6 +1977,22 @@ module Sequel
1937
1977
 
1938
1978
  private
1939
1979
 
1980
+ # Append the INSERT sql used in a MERGE
1981
+ def _merge_insert_sql(sql, data)
1982
+ sql << " THEN INSERT "
1983
+ columns, values = _parse_insert_sql_args(data[:values])
1984
+ _insert_columns_sql(sql, columns)
1985
+ if override = data[:override]
1986
+ sql << override
1987
+ end
1988
+ _insert_values_sql(sql, values)
1989
+ end
1990
+
1991
+ def _merge_matched_sql(sql, data)
1992
+ sql << " THEN DO NOTHING"
1993
+ end
1994
+ alias _merge_not_matched_sql _merge_matched_sql
1995
+
1940
1996
  # Format TRUNCATE statement with PostgreSQL specific options.
1941
1997
  def _truncate_sql(table)
1942
1998
  to = @opts[:truncate_opts] || OPTS
@@ -2013,14 +2069,13 @@ module Sequel
2013
2069
  end
2014
2070
 
2015
2071
  # Support OVERRIDING SYSTEM|USER VALUE in insert statements
2016
- def insert_values_sql(sql)
2072
+ def insert_override_sql(sql)
2017
2073
  case opts[:override]
2018
2074
  when :system
2019
2075
  sql << " OVERRIDING SYSTEM VALUE"
2020
2076
  when :user
2021
2077
  sql << " OVERRIDING USER VALUE"
2022
2078
  end
2023
- super
2024
2079
  end
2025
2080
 
2026
2081
  # For multiple table support, PostgreSQL requires at least
@@ -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"
@@ -76,7 +76,7 @@
76
76
  #
77
77
  # This extension integrates with the pg_array extension. If you plan
78
78
  # to use arrays of hstore types, load the pg_array extension before the
79
- # pg_interval extension:
79
+ # pg_hstore extension:
80
80
  #
81
81
  # DB.extension :pg_array, :pg_hstore
82
82
  #
@@ -6,7 +6,7 @@ module Sequel
6
6
 
7
7
  # The minor version of Sequel. Bumped for every non-patch level
8
8
  # release, generally around once a month.
9
- MINOR = 57
9
+ MINOR = 58
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.57.0
4
+ version: 5.58.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-01 00:00:00.000000000 Z
11
+ date: 2022-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -52,20 +52,6 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: minitest-shared_description
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: tzinfo
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -202,6 +188,7 @@ extra_rdoc_files:
202
188
  - doc/release_notes/5.55.0.txt
203
189
  - doc/release_notes/5.56.0.txt
204
190
  - doc/release_notes/5.57.0.txt
191
+ - doc/release_notes/5.58.0.txt
205
192
  - doc/release_notes/5.6.0.txt
206
193
  - doc/release_notes/5.7.0.txt
207
194
  - doc/release_notes/5.8.0.txt
@@ -287,6 +274,7 @@ files:
287
274
  - doc/release_notes/5.55.0.txt
288
275
  - doc/release_notes/5.56.0.txt
289
276
  - doc/release_notes/5.57.0.txt
277
+ - doc/release_notes/5.58.0.txt
290
278
  - doc/release_notes/5.6.0.txt
291
279
  - doc/release_notes/5.7.0.txt
292
280
  - doc/release_notes/5.8.0.txt