sequel 5.56.0 → 5.59.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +32 -0
- data/README.rdoc +25 -0
- data/doc/cheat_sheet.rdoc +8 -0
- data/doc/opening_databases.rdoc +10 -6
- data/doc/release_notes/5.57.0.txt +23 -0
- data/doc/release_notes/5.58.0.txt +31 -0
- data/doc/release_notes/5.59.0.txt +73 -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/postgres.rb +12 -6
- 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 +12 -0
- data/lib/sequel/adapters/shared/oracle.rb +69 -0
- data/lib/sequel/adapters/shared/postgres.rb +70 -4
- data/lib/sequel/database/schema_generator.rb +4 -0
- data/lib/sequel/database/schema_methods.rb +3 -0
- 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/is_distinct_from.rb +139 -0
- data/lib/sequel/extensions/pg_hstore.rb +1 -1
- data/lib/sequel/extensions/pg_json_ops.rb +52 -0
- data/lib/sequel/model/associations.rb +12 -0
- data/lib/sequel/model/base.rb +14 -4
- data/lib/sequel/plugins/insert_conflict.rb +4 -0
- data/lib/sequel/plugins/list.rb +3 -1
- data/lib/sequel/plugins/require_valid_schema.rb +67 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +7 -0
- data/lib/sequel/version.rb +1 -1
- metadata +10 -16
@@ -414,6 +414,7 @@ module Sequel
|
|
414
414
|
# 2 :: argument name
|
415
415
|
# 3 :: argument mode (e.g. in, out, inout)
|
416
416
|
# :behavior :: Should be IMMUTABLE, STABLE, or VOLATILE. PostgreSQL assumes VOLATILE by default.
|
417
|
+
# :parallel :: The thread safety attribute of the function. Should be SAFE, UNSAFE, RESTRICTED. PostgreSQL assumes UNSAFE by default.
|
417
418
|
# :cost :: The estimated cost of the function, used by the query planner.
|
418
419
|
# :language :: The language the function uses. SQL is the default.
|
419
420
|
# :link_symbol :: For a dynamically loaded see function, the function's link symbol if different from the definition argument.
|
@@ -1117,6 +1118,7 @@ module Sequel
|
|
1117
1118
|
#{opts[:behavior].to_s.upcase if opts[:behavior]}
|
1118
1119
|
#{'STRICT' if opts[:strict]}
|
1119
1120
|
#{'SECURITY DEFINER' if opts[:security_definer]}
|
1121
|
+
#{"PARALLEL #{opts[:parallel].to_s.upcase}" if opts[:parallel]}
|
1120
1122
|
#{"COST #{opts[:cost]}" if opts[:cost]}
|
1121
1123
|
#{"ROWS #{opts[:rows]}" if opts[:rows]}
|
1122
1124
|
#{opts[:set].map{|k,v| " SET #{k} = #{v}"}.join("\n") if opts[:set]}
|
@@ -1245,6 +1247,10 @@ module Sequel
|
|
1245
1247
|
def create_view_prefix_sql(name, options)
|
1246
1248
|
sql = create_view_sql_append_columns("CREATE #{'OR REPLACE 'if options[:replace]}#{'TEMPORARY 'if options[:temp]}#{'RECURSIVE ' if options[:recursive]}#{'MATERIALIZED ' if options[:materialized]}VIEW #{quote_schema_table(name)}", options[:columns] || options[:recursive])
|
1247
1249
|
|
1250
|
+
if options[:security_invoker]
|
1251
|
+
sql += " WITH (security_invoker)"
|
1252
|
+
end
|
1253
|
+
|
1248
1254
|
if tablespace = options[:tablespace]
|
1249
1255
|
sql += " TABLESPACE #{quote_identifier(tablespace)}"
|
1250
1256
|
end
|
@@ -1302,16 +1308,20 @@ module Sequel
|
|
1302
1308
|
def index_definition_sql(table_name, index)
|
1303
1309
|
cols = index[:columns]
|
1304
1310
|
index_name = index[:name] || default_index_name(table_name, cols)
|
1311
|
+
|
1305
1312
|
expr = if o = index[:opclass]
|
1306
1313
|
"(#{Array(cols).map{|c| "#{literal(c)} #{o}"}.join(', ')})"
|
1307
1314
|
else
|
1308
1315
|
literal(Array(cols))
|
1309
1316
|
end
|
1317
|
+
|
1310
1318
|
if_not_exists = " IF NOT EXISTS" if index[:if_not_exists]
|
1311
1319
|
unique = "UNIQUE " if index[:unique]
|
1312
1320
|
index_type = index[:type]
|
1313
1321
|
filter = index[:where] || index[:filter]
|
1314
1322
|
filter = " WHERE #{filter_expr(filter)}" if filter
|
1323
|
+
nulls_distinct = " NULLS#{' NOT' if index[:nulls_distinct] == false} DISTINCT" unless index[:nulls_distinct].nil?
|
1324
|
+
|
1315
1325
|
case index_type
|
1316
1326
|
when :full_text
|
1317
1327
|
expr = "(to_tsvector(#{literal(index[:language] || 'simple')}::regconfig, #{literal(dataset.send(:full_text_string_join, cols))}))"
|
@@ -1319,7 +1329,8 @@ module Sequel
|
|
1319
1329
|
when :spatial
|
1320
1330
|
index_type = :gist
|
1321
1331
|
end
|
1322
|
-
|
1332
|
+
|
1333
|
+
"CREATE #{unique}INDEX#{' CONCURRENTLY' if index[:concurrently]}#{if_not_exists} #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{" INCLUDE #{literal(Array(index[:include]))}" if index[:include]}#{nulls_distinct}#{" TABLESPACE #{quote_identifier(index[:tablespace])}" if index[:tablespace]}#{filter}"
|
1323
1334
|
end
|
1324
1335
|
|
1325
1336
|
# Setup datastructures shared by all postgres adapters.
|
@@ -1525,7 +1536,7 @@ module Sequel
|
|
1525
1536
|
LOCK_MODES = ['ACCESS SHARE', 'ROW SHARE', 'ROW EXCLUSIVE', 'SHARE UPDATE EXCLUSIVE', 'SHARE', 'SHARE ROW EXCLUSIVE', 'EXCLUSIVE', 'ACCESS EXCLUSIVE'].each(&:freeze).freeze
|
1526
1537
|
|
1527
1538
|
Dataset.def_sql_method(self, :delete, [['if server_version >= 90100', %w'with delete from using where returning'], ['else', %w'delete from using where returning']])
|
1528
|
-
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']])
|
1539
|
+
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']])
|
1529
1540
|
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']])
|
1530
1541
|
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']])
|
1531
1542
|
|
@@ -1758,6 +1769,41 @@ module Sequel
|
|
1758
1769
|
nil
|
1759
1770
|
end
|
1760
1771
|
|
1772
|
+
# Return a dataset with a WHEN MATCHED THEN DO NOTHING clause added to the
|
1773
|
+
# MERGE statement. If a block is passed, treat it as a virtual row and
|
1774
|
+
# use it as additional conditions for the match.
|
1775
|
+
#
|
1776
|
+
# merge_do_nothing_when_matched
|
1777
|
+
# # WHEN MATCHED THEN DO NOTHING
|
1778
|
+
#
|
1779
|
+
# merge_do_nothing_when_matched{a > 30}
|
1780
|
+
# # WHEN MATCHED AND (a > 30) THEN DO NOTHING
|
1781
|
+
def merge_do_nothing_when_matched(&block)
|
1782
|
+
_merge_when(:type=>:matched, &block)
|
1783
|
+
end
|
1784
|
+
|
1785
|
+
# Return a dataset with a WHEN NOT MATCHED THEN DO NOTHING clause added to the
|
1786
|
+
# MERGE statement. If a block is passed, treat it as a virtual row and
|
1787
|
+
# use it as additional conditions for the match.
|
1788
|
+
#
|
1789
|
+
# merge_do_nothing_when_not_matched
|
1790
|
+
# # WHEN NOT MATCHED THEN DO NOTHING
|
1791
|
+
#
|
1792
|
+
# merge_do_nothing_when_not_matched{a > 30}
|
1793
|
+
# # WHEN NOT MATCHED AND (a > 30) THEN DO NOTHING
|
1794
|
+
def merge_do_nothing_when_not_matched(&block)
|
1795
|
+
_merge_when(:type=>:not_matched, &block)
|
1796
|
+
end
|
1797
|
+
|
1798
|
+
# Support OVERRIDING USER|SYSTEM VALUE for MERGE INSERT.
|
1799
|
+
def merge_insert(*values, &block)
|
1800
|
+
h = {:type=>:insert, :values=>values}
|
1801
|
+
if override = @opts[:override]
|
1802
|
+
h[:override] = insert_override_sql(String.new)
|
1803
|
+
end
|
1804
|
+
_merge_when(h, &block)
|
1805
|
+
end
|
1806
|
+
|
1761
1807
|
# Use OVERRIDING USER VALUE for INSERT statements, so that identity columns
|
1762
1808
|
# always use the user supplied value, and an error is not raised for identity
|
1763
1809
|
# columns that are GENERATED ALWAYS.
|
@@ -1825,6 +1871,11 @@ module Sequel
|
|
1825
1871
|
true
|
1826
1872
|
end
|
1827
1873
|
|
1874
|
+
# PostgreSQL 15+ supports MERGE.
|
1875
|
+
def supports_merge?
|
1876
|
+
server_version >= 150000
|
1877
|
+
end
|
1878
|
+
|
1828
1879
|
# PostgreSQL supports NOWAIT.
|
1829
1880
|
def supports_nowait?
|
1830
1881
|
true
|
@@ -1935,6 +1986,22 @@ module Sequel
|
|
1935
1986
|
|
1936
1987
|
private
|
1937
1988
|
|
1989
|
+
# Append the INSERT sql used in a MERGE
|
1990
|
+
def _merge_insert_sql(sql, data)
|
1991
|
+
sql << " THEN INSERT "
|
1992
|
+
columns, values = _parse_insert_sql_args(data[:values])
|
1993
|
+
_insert_columns_sql(sql, columns)
|
1994
|
+
if override = data[:override]
|
1995
|
+
sql << override
|
1996
|
+
end
|
1997
|
+
_insert_values_sql(sql, values)
|
1998
|
+
end
|
1999
|
+
|
2000
|
+
def _merge_matched_sql(sql, data)
|
2001
|
+
sql << " THEN DO NOTHING"
|
2002
|
+
end
|
2003
|
+
alias _merge_not_matched_sql _merge_matched_sql
|
2004
|
+
|
1938
2005
|
# Format TRUNCATE statement with PostgreSQL specific options.
|
1939
2006
|
def _truncate_sql(table)
|
1940
2007
|
to = @opts[:truncate_opts] || OPTS
|
@@ -2011,14 +2078,13 @@ module Sequel
|
|
2011
2078
|
end
|
2012
2079
|
|
2013
2080
|
# Support OVERRIDING SYSTEM|USER VALUE in insert statements
|
2014
|
-
def
|
2081
|
+
def insert_override_sql(sql)
|
2015
2082
|
case opts[:override]
|
2016
2083
|
when :system
|
2017
2084
|
sql << " OVERRIDING SYSTEM VALUE"
|
2018
2085
|
when :user
|
2019
2086
|
sql << " OVERRIDING USER VALUE"
|
2020
2087
|
end
|
2021
|
-
super
|
2022
2088
|
end
|
2023
2089
|
|
2024
2090
|
# For multiple table support, PostgreSQL requires at least
|
@@ -146,6 +146,9 @@ module Sequel
|
|
146
146
|
#
|
147
147
|
# :generated_type :: Set the type of column when using :generated_always_as,
|
148
148
|
# should be :virtual or :stored to force a type.
|
149
|
+
# :on_update_current_timestamp :: Use ON UPDATE CURRENT TIMESTAMP when defining the column,
|
150
|
+
# which will update the column value to CURRENT_TIMESTAMP
|
151
|
+
# on every UPDATE.
|
149
152
|
#
|
150
153
|
# Microsoft SQL Server specific options:
|
151
154
|
#
|
@@ -259,6 +262,7 @@ module Sequel
|
|
259
262
|
# operations on the table while the index is being
|
260
263
|
# built.
|
261
264
|
# :if_not_exists :: Only create the index if an index of the same name doesn't already exist.
|
265
|
+
# :nulls_distinct :: Set whether separate NULLs should be considered distinct values in unique indexes.
|
262
266
|
# :opclass :: Set an opclass to use for all columns (per-column opclasses require
|
263
267
|
# custom SQL).
|
264
268
|
# :tablespace :: Specify tablespace for index.
|
@@ -295,6 +295,9 @@ module Sequel
|
|
295
295
|
# in a subquery, if you are providing a Dataset as the source
|
296
296
|
# argument, if should probably call the union method with the
|
297
297
|
# all: true and from_self: false options.
|
298
|
+
# :security_invoker :: Set the security_invoker property on the view, making
|
299
|
+
# the access to the view use the current user's permissions,
|
300
|
+
# instead of the view owner's permissions.
|
298
301
|
# :tablespace :: The tablespace to use for materialized views.
|
299
302
|
def create_view(name, source, options = OPTS)
|
300
303
|
execute_ddl(create_view_sql(name, source, options))
|
@@ -457,6 +457,55 @@ module Sequel
|
|
457
457
|
_aggregate(:max, arg)
|
458
458
|
end
|
459
459
|
|
460
|
+
# Execute a MERGE statement, which allows for INSERT, UPDATE, and DELETE
|
461
|
+
# behavior in a single query, based on whether rows from a source table
|
462
|
+
# match rows in the current table, based on the join conditions.
|
463
|
+
#
|
464
|
+
# Unless the dataset uses static SQL, to use #merge, you must first have
|
465
|
+
# called #merge_using to specify the merge source and join conditions.
|
466
|
+
# You will then likely to call one or more of the following methods
|
467
|
+
# to specify MERGE behavior by adding WHEN [NOT] MATCHED clauses:
|
468
|
+
#
|
469
|
+
# * #merge_insert
|
470
|
+
# * #merge_update
|
471
|
+
# * #merge_delete
|
472
|
+
#
|
473
|
+
# The WHEN [NOT] MATCHED clauses are added to the SQL in the order these
|
474
|
+
# methods were called on the dataset. If none of these methods are
|
475
|
+
# called, an error is raised.
|
476
|
+
#
|
477
|
+
# Example:
|
478
|
+
#
|
479
|
+
# DB[:m1]
|
480
|
+
# merge_using(:m2, i1: :i2).
|
481
|
+
# merge_insert(i1: :i2, a: Sequel[:b]+11).
|
482
|
+
# merge_delete{a > 30}.
|
483
|
+
# merge_update(i1: Sequel[:i1]+:i2+10, a: Sequel[:a]+:b+20).
|
484
|
+
# merge
|
485
|
+
#
|
486
|
+
# SQL:
|
487
|
+
#
|
488
|
+
# MERGE INTO m1 USING m2 ON (i1 = i2)
|
489
|
+
# WHEN NOT MATCHED THEN INSERT (i1, a) VALUES (i2, (b + 11))
|
490
|
+
# WHEN MATCHED AND (a > 30) THEN DELETE
|
491
|
+
# WHEN MATCHED THEN UPDATE SET i1 = (i1 + i2 + 10), a = (a + b + 20)
|
492
|
+
#
|
493
|
+
# On PostgreSQL, two additional merge methods are supported, for the
|
494
|
+
# PostgreSQL-specific DO NOTHING syntax.
|
495
|
+
#
|
496
|
+
# * #merge_do_nothing_when_matched
|
497
|
+
# * #merge_do_nothing_when_not_matched
|
498
|
+
#
|
499
|
+
# This method is supported on Oracle, but Oracle's MERGE support is
|
500
|
+
# non-standard, and has the following issues:
|
501
|
+
#
|
502
|
+
# * DELETE clause requires UPDATE clause
|
503
|
+
# * DELETE clause requires a condition
|
504
|
+
# * DELETE clause only affects rows updated by UPDATE clause
|
505
|
+
def merge
|
506
|
+
execute_ddl(merge_sql)
|
507
|
+
end
|
508
|
+
|
460
509
|
# Returns the minimum value for the given column/expression.
|
461
510
|
# Uses a virtual row block if no argument is given.
|
462
511
|
#
|
@@ -125,6 +125,11 @@ module Sequel
|
|
125
125
|
false
|
126
126
|
end
|
127
127
|
|
128
|
+
# Whether the MERGE statement is supported, false by default.
|
129
|
+
def supports_merge?
|
130
|
+
false
|
131
|
+
end
|
132
|
+
|
128
133
|
# Whether modifying joined datasets is supported, false by default.
|
129
134
|
def supports_modifying_joins?
|
130
135
|
false
|
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"
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The is_distinct_from extension adds the ability to use the
|
4
|
+
# SQL standard IS DISTINCT FROM operator, which is similar to the
|
5
|
+
# not equals operator, except that NULL values are considered
|
6
|
+
# equal. Only PostgreSQL and H2 currently support this operator. On
|
7
|
+
# other databases, support is emulated.
|
8
|
+
#
|
9
|
+
# First, you need to load the extension into the database:
|
10
|
+
#
|
11
|
+
# DB.extension :is_distinct_from
|
12
|
+
#
|
13
|
+
# Then you can use the Sequel.is_distinct_from to create the expression
|
14
|
+
# objects:
|
15
|
+
#
|
16
|
+
# expr = Sequel.is_distinct_from(:column_a, :column_b)
|
17
|
+
# # (column_a IS DISTINCT FROM column_b)
|
18
|
+
#
|
19
|
+
# You can also use the +is_distinct_from+ method on most Sequel expressions:
|
20
|
+
#
|
21
|
+
# expr = Sequel[:column_a].is_distinct_from(:column_b)
|
22
|
+
# # (column_a IS DISTINCT FROM column_b)
|
23
|
+
#
|
24
|
+
# These expressions can be used in your datasets, or anywhere else that
|
25
|
+
# Sequel expressions are allowed:
|
26
|
+
#
|
27
|
+
# DB[:table].where(expr)
|
28
|
+
#
|
29
|
+
# Related module: Sequel::SQL::IsDistinctFrom
|
30
|
+
|
31
|
+
#
|
32
|
+
module Sequel
|
33
|
+
module SQL
|
34
|
+
module Builders
|
35
|
+
# Return a IsDistinctFrom expression object, using the IS DISTINCT FROM operator
|
36
|
+
# with the given left hand side and right hand side.
|
37
|
+
def is_distinct_from(lhs, rhs)
|
38
|
+
BooleanExpression.new(:NOOP, IsDistinctFrom.new(lhs, rhs))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Represents an SQL expression using the IS DISTINCT FROM operator.
|
43
|
+
class IsDistinctFrom < GenericExpression
|
44
|
+
# These methods are added to expressions, allowing them to return IS DISTINCT
|
45
|
+
# FROM expressions based on the receiving expression.
|
46
|
+
module Methods
|
47
|
+
# Return a IsDistinctFrom expression, using the IS DISTINCT FROM operator,
|
48
|
+
# with the receiver as the left hand side and the argument as the right hand side.
|
49
|
+
def is_distinct_from(rhs)
|
50
|
+
BooleanExpression.new(:NOOP, IsDistinctFrom.new(self, rhs))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# These methods are added to datasets using the is_distinct_from extension
|
55
|
+
# extension, for the purposes of correctly literalizing IsDistinctFrom
|
56
|
+
# expressions for the appropriate database type.
|
57
|
+
module DatasetMethods
|
58
|
+
# Append the SQL fragment for the IS DISTINCT FROM expression to the SQL query.
|
59
|
+
def is_distinct_from_sql_append(sql, idf)
|
60
|
+
lhs = idf.lhs
|
61
|
+
rhs = idf.rhs
|
62
|
+
|
63
|
+
if supports_is_distinct_from?
|
64
|
+
sql << "("
|
65
|
+
literal_append(sql, lhs)
|
66
|
+
sql << " IS DISTINCT FROM "
|
67
|
+
literal_append(sql, rhs)
|
68
|
+
sql << ")"
|
69
|
+
elsif db.database_type == :derby && (lhs == nil || rhs == nil)
|
70
|
+
if lhs == nil && rhs == nil
|
71
|
+
sql << literal_false
|
72
|
+
elsif lhs == nil
|
73
|
+
literal_append(sql, ~Sequel.expr(rhs=>nil))
|
74
|
+
else
|
75
|
+
literal_append(sql, ~Sequel.expr(lhs=>nil))
|
76
|
+
end
|
77
|
+
else
|
78
|
+
literal_append(sql, Sequel.case({(Sequel.expr(lhs=>rhs) | [[lhs, nil], [rhs, nil]]) => 0}, 1) => 1)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Whether the database supports IS DISTINCT FROM.
|
85
|
+
def supports_is_distinct_from?
|
86
|
+
if defined?(super)
|
87
|
+
return super
|
88
|
+
end
|
89
|
+
|
90
|
+
case db.database_type
|
91
|
+
when :postgres, :h2
|
92
|
+
true
|
93
|
+
else
|
94
|
+
false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# The left hand side of the IS DISTINCT FROM expression.
|
100
|
+
attr_reader :lhs
|
101
|
+
|
102
|
+
# The right hand side of the IS DISTINCT FROM expression.
|
103
|
+
attr_reader :rhs
|
104
|
+
|
105
|
+
def initialize(lhs, rhs)
|
106
|
+
@lhs = lhs
|
107
|
+
@rhs = rhs
|
108
|
+
end
|
109
|
+
|
110
|
+
to_s_method :is_distinct_from_sql
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class SQL::GenericExpression
|
115
|
+
include SQL::IsDistinctFrom::Methods
|
116
|
+
end
|
117
|
+
|
118
|
+
class LiteralString
|
119
|
+
include SQL::IsDistinctFrom::Methods
|
120
|
+
end
|
121
|
+
|
122
|
+
Dataset.register_extension(:is_distinct_from, SQL::IsDistinctFrom::DatasetMethods)
|
123
|
+
end
|
124
|
+
|
125
|
+
# :nocov:
|
126
|
+
if Sequel.core_extensions?
|
127
|
+
class Symbol
|
128
|
+
include Sequel::SQL::IsDistinctFrom::Methods
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
if defined?(Sequel::CoreRefinements)
|
133
|
+
module Sequel::CoreRefinements
|
134
|
+
refine Symbol do
|
135
|
+
send INCLUDE_METH, Sequel::SQL::IsDistinctFrom::Methods
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
# :nocov:
|