sequel 5.57.0 → 5.58.0

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