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 +4 -4
- data/CHANGELOG +6 -0
- data/README.rdoc +25 -0
- data/doc/cheat_sheet.rdoc +8 -0
- data/doc/opening_databases.rdoc +6 -1
- data/doc/release_notes/5.58.0.txt +31 -0
- data/doc/testing.rdoc +1 -1
- data/lib/sequel/adapters/jdbc/derby.rb +5 -0
- data/lib/sequel/adapters/jdbc/h2.rb +5 -0
- data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
- data/lib/sequel/adapters/shared/db2.rb +28 -0
- data/lib/sequel/adapters/shared/mssql.rb +34 -0
- data/lib/sequel/adapters/shared/mysql.rb +6 -0
- data/lib/sequel/adapters/shared/oracle.rb +69 -0
- data/lib/sequel/adapters/shared/postgres.rb +58 -3
- data/lib/sequel/dataset/actions.rb +49 -0
- data/lib/sequel/dataset/features.rb +5 -0
- data/lib/sequel/dataset/query.rb +62 -0
- data/lib/sequel/dataset/sql.rb +112 -25
- data/lib/sequel/extensions/pg_hstore.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- metadata +4 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2f02f6a35677e8af2ea2b164f655cc869213864e5b27f67cdd3e17c7db28c546
|
4
|
+
data.tar.gz: ee09f1b4d5b3f4decbf4d4af3b8ae16ef7e488af40011808dac94f8ca05da89f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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]}
|
data/doc/opening_databases.rdoc
CHANGED
@@ -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
|
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
|
|
@@ -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
|
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
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -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
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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"
|
data/lib/sequel/version.rb
CHANGED
@@ -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 =
|
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.
|
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-
|
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
|