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 +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
|