sequel 5.57.0 → 5.60.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +34 -0
- data/README.rdoc +25 -0
- data/bin/sequel +11 -3
- data/doc/cheat_sheet.rdoc +8 -0
- data/doc/opening_databases.rdoc +10 -6
- data/doc/release_notes/5.58.0.txt +31 -0
- data/doc/release_notes/5.59.0.txt +73 -0
- data/doc/release_notes/5.60.0.txt +22 -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/jdbc/sqlite.rb +1 -1
- data/lib/sequel/adapters/jdbc.rb +5 -5
- data/lib/sequel/adapters/mock.rb +1 -1
- data/lib/sequel/adapters/mysql.rb +3 -3
- data/lib/sequel/adapters/oracle.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +58 -17
- data/lib/sequel/adapters/shared/db2.rb +28 -0
- data/lib/sequel/adapters/shared/mssql.rb +35 -1
- data/lib/sequel/adapters/shared/mysql.rb +6 -0
- data/lib/sequel/adapters/shared/oracle.rb +70 -1
- data/lib/sequel/adapters/shared/postgres.rb +94 -18
- data/lib/sequel/adapters/shared/sqlite.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +1 -1
- data/lib/sequel/ast_transformer.rb +1 -1
- data/lib/sequel/database/misc.rb +2 -2
- data/lib/sequel/database/schema_generator.rb +1 -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 +114 -27
- data/lib/sequel/extensions/date_arithmetic.rb +35 -7
- data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
- data/lib/sequel/extensions/is_distinct_from.rb +3 -1
- data/lib/sequel/extensions/pg_array.rb +2 -2
- data/lib/sequel/extensions/pg_array_ops.rb +1 -1
- data/lib/sequel/extensions/pg_enum.rb +1 -1
- data/lib/sequel/extensions/pg_hstore.rb +1 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +3 -3
- data/lib/sequel/extensions/pg_inet.rb +2 -2
- data/lib/sequel/extensions/pg_interval.rb +1 -1
- data/lib/sequel/extensions/pg_json.rb +1 -1
- data/lib/sequel/extensions/pg_json_ops.rb +55 -3
- data/lib/sequel/extensions/pg_multirange.rb +2 -2
- data/lib/sequel/extensions/pg_range.rb +2 -2
- data/lib/sequel/extensions/pg_row.rb +2 -2
- data/lib/sequel/extensions/pg_static_cache_updater.rb +2 -2
- data/lib/sequel/extensions/symbol_aref.rb +2 -0
- data/lib/sequel/model/associations.rb +18 -6
- data/lib/sequel/model/base.rb +17 -7
- data/lib/sequel/model/exceptions.rb +1 -1
- data/lib/sequel/model/inflections.rb +6 -6
- data/lib/sequel/plugins/auto_validations.rb +1 -1
- data/lib/sequel/plugins/defaults_setter.rb +1 -1
- data/lib/sequel/plugins/dirty.rb +1 -1
- data/lib/sequel/plugins/insert_conflict.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +1 -1
- data/lib/sequel/plugins/list.rb +3 -1
- data/lib/sequel/plugins/nested_attributes.rb +1 -1
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +1 -1
- data/lib/sequel/plugins/require_valid_schema.rb +67 -0
- data/lib/sequel/plugins/serialization.rb +1 -1
- data/lib/sequel/plugins/sharding.rb +1 -1
- data/lib/sequel/plugins/sql_comments.rb +4 -4
- data/lib/sequel/plugins/subclasses.rb +1 -1
- data/lib/sequel/plugins/tactical_eager_loading.rb +7 -0
- data/lib/sequel/plugins/validation_class_methods.rb +3 -3
- data/lib/sequel/plugins/validation_helpers.rb +1 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- metadata +9 -16
@@ -230,7 +230,6 @@ module Sequel
|
|
230
230
|
module DatabaseMethods
|
231
231
|
include UnmodifiedIdentifiers::DatabaseMethods
|
232
232
|
|
233
|
-
PREPARED_ARG_PLACEHOLDER = LiteralString.new('$').freeze
|
234
233
|
FOREIGN_KEY_LIST_ON_DELETE_MAP = {'a'=>:no_action, 'r'=>:restrict, 'c'=>:cascade, 'n'=>:set_null, 'd'=>:set_default}.freeze
|
235
234
|
ON_COMMIT = {:drop => 'DROP', :delete_rows => 'DELETE ROWS', :preserve_rows => 'PRESERVE ROWS'}.freeze
|
236
235
|
ON_COMMIT.each_value(&:freeze)
|
@@ -365,9 +364,10 @@ module Sequel
|
|
365
364
|
|
366
365
|
table_oid = regclass_oid(table)
|
367
366
|
im = input_identifier_meth
|
368
|
-
unless column =
|
367
|
+
unless column = (opts[:column] || ((sch = schema(table).find{|_, sc| sc[:primary_key] && sc[:auto_increment]}) && sch[0]))
|
369
368
|
raise Error, "could not determine column to convert from serial to identity automatically"
|
370
369
|
end
|
370
|
+
column = im.call(column)
|
371
371
|
|
372
372
|
column_num = ds.from(:pg_attribute).
|
373
373
|
where(:attrelid=>table_oid, :attname=>column).
|
@@ -569,10 +569,12 @@ module Sequel
|
|
569
569
|
if server_version >= 90500
|
570
570
|
cpos = Sequel.expr{array_position(co[:conkey], ctable[:attnum])}
|
571
571
|
rpos = Sequel.expr{array_position(co[:confkey], rtable[:attnum])}
|
572
|
+
# :nocov:
|
572
573
|
else
|
573
574
|
range = 0...32
|
574
575
|
cpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:conkey], [x]), x]}, 32, ctable[:attnum])}
|
575
576
|
rpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:confkey], [x]), x]}, 32, rtable[:attnum])}
|
577
|
+
# :nocov:
|
576
578
|
end
|
577
579
|
|
578
580
|
ds = metadata_dataset.
|
@@ -653,9 +655,11 @@ module Sequel
|
|
653
655
|
|
654
656
|
if server_version >= 90500
|
655
657
|
order = [Sequel[:indc][:relname], Sequel.function(:array_position, Sequel[:ind][:indkey], Sequel[:att][:attnum])]
|
658
|
+
# :nocov:
|
656
659
|
else
|
657
660
|
range = 0...32
|
658
661
|
order = [Sequel[:indc][:relname], SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(Sequel[:ind][:indkey], [x]), x]}, 32, Sequel[:att][:attnum])]
|
662
|
+
# :nocov:
|
659
663
|
end
|
660
664
|
|
661
665
|
attnums = SQL::Function.new(:ANY, Sequel[:ind][:indkey])
|
@@ -676,8 +680,10 @@ module Sequel
|
|
676
680
|
select{[indc[:relname].as(:name), ind[:indisunique].as(:unique), att[:attname].as(:column), con[:condeferrable].as(:deferrable)]}
|
677
681
|
|
678
682
|
ds = ds.where(:indpred=>nil) unless opts[:include_partial]
|
683
|
+
# :nocov:
|
679
684
|
ds = ds.where(:indisready=>true) if server_version >= 80300
|
680
685
|
ds = ds.where(:indislive=>true) if server_version >= 90300
|
686
|
+
# :nocov:
|
681
687
|
|
682
688
|
indexes = {}
|
683
689
|
ds.each do |r|
|
@@ -758,10 +764,12 @@ module Sequel
|
|
758
764
|
seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq)))
|
759
765
|
increment_by = :seqincrement
|
760
766
|
min_value = :seqmin
|
767
|
+
# :nocov:
|
761
768
|
else
|
762
769
|
seq_ds = metadata_dataset.from(LiteralString.new(seq))
|
763
770
|
increment_by = :increment_by
|
764
771
|
min_value = :min_value
|
772
|
+
# :nocov:
|
765
773
|
end
|
766
774
|
|
767
775
|
get{setval(seq, db[table].select(coalesce(max(pk)+seq_ds.select(increment_by), seq_ds.select(min_value))), false)}
|
@@ -774,7 +782,9 @@ module Sequel
|
|
774
782
|
# PostgreSQL uses SERIAL psuedo-type instead of AUTOINCREMENT for
|
775
783
|
# managing incrementing primary keys.
|
776
784
|
def serial_primary_key_options
|
785
|
+
# :nocov:
|
777
786
|
auto_increment_key = server_version >= 100002 ? :identity : :serial
|
787
|
+
# :nocov:
|
778
788
|
{:primary_key => true, auto_increment_key => true, :type=>Integer}
|
779
789
|
end
|
780
790
|
|
@@ -1152,7 +1162,7 @@ module Sequel
|
|
1152
1162
|
when :hash
|
1153
1163
|
mod, remainder = generator.hash_values
|
1154
1164
|
sql << " FOR VALUES WITH (MODULUS #{literal(mod)}, REMAINDER #{literal(remainder)})"
|
1155
|
-
when :default
|
1165
|
+
else # when :default
|
1156
1166
|
sql << " DEFAULT"
|
1157
1167
|
end
|
1158
1168
|
|
@@ -1247,6 +1257,10 @@ module Sequel
|
|
1247
1257
|
def create_view_prefix_sql(name, options)
|
1248
1258
|
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])
|
1249
1259
|
|
1260
|
+
if options[:security_invoker]
|
1261
|
+
sql += " WITH (security_invoker)"
|
1262
|
+
end
|
1263
|
+
|
1250
1264
|
if tablespace = options[:tablespace]
|
1251
1265
|
sql += " TABLESPACE #{quote_identifier(tablespace)}"
|
1252
1266
|
end
|
@@ -1304,16 +1318,20 @@ module Sequel
|
|
1304
1318
|
def index_definition_sql(table_name, index)
|
1305
1319
|
cols = index[:columns]
|
1306
1320
|
index_name = index[:name] || default_index_name(table_name, cols)
|
1321
|
+
|
1307
1322
|
expr = if o = index[:opclass]
|
1308
1323
|
"(#{Array(cols).map{|c| "#{literal(c)} #{o}"}.join(', ')})"
|
1309
1324
|
else
|
1310
1325
|
literal(Array(cols))
|
1311
1326
|
end
|
1327
|
+
|
1312
1328
|
if_not_exists = " IF NOT EXISTS" if index[:if_not_exists]
|
1313
1329
|
unique = "UNIQUE " if index[:unique]
|
1314
1330
|
index_type = index[:type]
|
1315
1331
|
filter = index[:where] || index[:filter]
|
1316
1332
|
filter = " WHERE #{filter_expr(filter)}" if filter
|
1333
|
+
nulls_distinct = " NULLS#{' NOT' if index[:nulls_distinct] == false} DISTINCT" unless index[:nulls_distinct].nil?
|
1334
|
+
|
1317
1335
|
case index_type
|
1318
1336
|
when :full_text
|
1319
1337
|
expr = "(to_tsvector(#{literal(index[:language] || 'simple')}::regconfig, #{literal(dataset.send(:full_text_string_join, cols))}))"
|
@@ -1321,7 +1339,8 @@ module Sequel
|
|
1321
1339
|
when :spatial
|
1322
1340
|
index_type = :gist
|
1323
1341
|
end
|
1324
|
-
|
1342
|
+
|
1343
|
+
"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}"
|
1325
1344
|
end
|
1326
1345
|
|
1327
1346
|
# Setup datastructures shared by all postgres adapters.
|
@@ -1347,11 +1366,6 @@ module Sequel
|
|
1347
1366
|
end
|
1348
1367
|
end
|
1349
1368
|
|
1350
|
-
# Use a dollar sign instead of question mark for the argument placeholder.
|
1351
|
-
def prepared_arg_placeholder
|
1352
|
-
PREPARED_ARG_PLACEHOLDER
|
1353
|
-
end
|
1354
|
-
|
1355
1369
|
# Return an expression the oid for the table expr. Used by the metadata parsing
|
1356
1370
|
# code to disambiguate unqualified tables.
|
1357
1371
|
def regclass_oid(expr, opts=OPTS)
|
@@ -1425,10 +1439,14 @@ module Sequel
|
|
1425
1439
|
where{{pg_class[:oid]=>oid}}.
|
1426
1440
|
order{pg_attribute[:attnum]}
|
1427
1441
|
|
1442
|
+
# :nocov:
|
1428
1443
|
if server_version > 100000
|
1444
|
+
# :nocov:
|
1429
1445
|
ds = ds.select_append{pg_attribute[:attidentity]}
|
1430
1446
|
|
1447
|
+
# :nocov:
|
1431
1448
|
if server_version > 120000
|
1449
|
+
# :nocov:
|
1432
1450
|
ds = ds.select_append{Sequel.~(pg_attribute[:attgenerated]=>'').as(:generated)}
|
1433
1451
|
end
|
1434
1452
|
end
|
@@ -1516,7 +1534,9 @@ module Sequel
|
|
1516
1534
|
|
1517
1535
|
# PostgreSQL 9.4+ supports views with check option.
|
1518
1536
|
def view_with_check_option_support
|
1537
|
+
# :nocov:
|
1519
1538
|
:local if server_version >= 90400
|
1539
|
+
# :nocov:
|
1520
1540
|
end
|
1521
1541
|
end
|
1522
1542
|
|
@@ -1527,7 +1547,7 @@ module Sequel
|
|
1527
1547
|
LOCK_MODES = ['ACCESS SHARE', 'ROW SHARE', 'ROW EXCLUSIVE', 'SHARE UPDATE EXCLUSIVE', 'SHARE', 'SHARE ROW EXCLUSIVE', 'EXCLUSIVE', 'ACCESS EXCLUSIVE'].each(&:freeze).freeze
|
1528
1548
|
|
1529
1549
|
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']])
|
1550
|
+
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
1551
|
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
1552
|
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
1553
|
|
@@ -1760,6 +1780,41 @@ module Sequel
|
|
1760
1780
|
nil
|
1761
1781
|
end
|
1762
1782
|
|
1783
|
+
# Return a dataset with a WHEN MATCHED THEN DO NOTHING clause added to the
|
1784
|
+
# MERGE statement. If a block is passed, treat it as a virtual row and
|
1785
|
+
# use it as additional conditions for the match.
|
1786
|
+
#
|
1787
|
+
# merge_do_nothing_when_matched
|
1788
|
+
# # WHEN MATCHED THEN DO NOTHING
|
1789
|
+
#
|
1790
|
+
# merge_do_nothing_when_matched{a > 30}
|
1791
|
+
# # WHEN MATCHED AND (a > 30) THEN DO NOTHING
|
1792
|
+
def merge_do_nothing_when_matched(&block)
|
1793
|
+
_merge_when(:type=>:matched, &block)
|
1794
|
+
end
|
1795
|
+
|
1796
|
+
# Return a dataset with a WHEN NOT MATCHED THEN DO NOTHING clause added to the
|
1797
|
+
# MERGE statement. If a block is passed, treat it as a virtual row and
|
1798
|
+
# use it as additional conditions for the match.
|
1799
|
+
#
|
1800
|
+
# merge_do_nothing_when_not_matched
|
1801
|
+
# # WHEN NOT MATCHED THEN DO NOTHING
|
1802
|
+
#
|
1803
|
+
# merge_do_nothing_when_not_matched{a > 30}
|
1804
|
+
# # WHEN NOT MATCHED AND (a > 30) THEN DO NOTHING
|
1805
|
+
def merge_do_nothing_when_not_matched(&block)
|
1806
|
+
_merge_when(:type=>:not_matched, &block)
|
1807
|
+
end
|
1808
|
+
|
1809
|
+
# Support OVERRIDING USER|SYSTEM VALUE for MERGE INSERT.
|
1810
|
+
def merge_insert(*values, &block)
|
1811
|
+
h = {:type=>:insert, :values=>values}
|
1812
|
+
if override = @opts[:override]
|
1813
|
+
h[:override] = insert_override_sql(String.new)
|
1814
|
+
end
|
1815
|
+
_merge_when(h, &block)
|
1816
|
+
end
|
1817
|
+
|
1763
1818
|
# Use OVERRIDING USER VALUE for INSERT statements, so that identity columns
|
1764
1819
|
# always use the user supplied value, and an error is not raised for identity
|
1765
1820
|
# columns that are GENERATED ALWAYS.
|
@@ -1827,6 +1882,11 @@ module Sequel
|
|
1827
1882
|
true
|
1828
1883
|
end
|
1829
1884
|
|
1885
|
+
# PostgreSQL 15+ supports MERGE.
|
1886
|
+
def supports_merge?
|
1887
|
+
server_version >= 150000
|
1888
|
+
end
|
1889
|
+
|
1830
1890
|
# PostgreSQL supports NOWAIT.
|
1831
1891
|
def supports_nowait?
|
1832
1892
|
true
|
@@ -1872,6 +1932,8 @@ module Sequel
|
|
1872
1932
|
server_version >= 90000
|
1873
1933
|
when :groups, :exclude
|
1874
1934
|
server_version >= 110000
|
1935
|
+
else
|
1936
|
+
false
|
1875
1937
|
end
|
1876
1938
|
end
|
1877
1939
|
|
@@ -1937,6 +1999,22 @@ module Sequel
|
|
1937
1999
|
|
1938
2000
|
private
|
1939
2001
|
|
2002
|
+
# Append the INSERT sql used in a MERGE
|
2003
|
+
def _merge_insert_sql(sql, data)
|
2004
|
+
sql << " THEN INSERT "
|
2005
|
+
columns, values = _parse_insert_sql_args(data[:values])
|
2006
|
+
_insert_columns_sql(sql, columns)
|
2007
|
+
if override = data[:override]
|
2008
|
+
sql << override
|
2009
|
+
end
|
2010
|
+
_insert_values_sql(sql, values)
|
2011
|
+
end
|
2012
|
+
|
2013
|
+
def _merge_matched_sql(sql, data)
|
2014
|
+
sql << " THEN DO NOTHING"
|
2015
|
+
end
|
2016
|
+
alias _merge_not_matched_sql _merge_matched_sql
|
2017
|
+
|
1940
2018
|
# Format TRUNCATE statement with PostgreSQL specific options.
|
1941
2019
|
def _truncate_sql(table)
|
1942
2020
|
to = @opts[:truncate_opts] || OPTS
|
@@ -2002,25 +2080,23 @@ module Sequel
|
|
2002
2080
|
|
2003
2081
|
# Return the primary key to use for RETURNING in an INSERT statement
|
2004
2082
|
def insert_pk
|
2005
|
-
|
2006
|
-
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
2010
|
-
end
|
2083
|
+
(f = opts[:from]) && !f.empty? && (t = f.first)
|
2084
|
+
case t
|
2085
|
+
when Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier
|
2086
|
+
if pk = db.primary_key(t)
|
2087
|
+
Sequel::SQL::Identifier.new(pk)
|
2011
2088
|
end
|
2012
2089
|
end
|
2013
2090
|
end
|
2014
2091
|
|
2015
2092
|
# Support OVERRIDING SYSTEM|USER VALUE in insert statements
|
2016
|
-
def
|
2093
|
+
def insert_override_sql(sql)
|
2017
2094
|
case opts[:override]
|
2018
2095
|
when :system
|
2019
2096
|
sql << " OVERRIDING SYSTEM VALUE"
|
2020
2097
|
when :user
|
2021
2098
|
sql << " OVERRIDING USER VALUE"
|
2022
2099
|
end
|
2023
|
-
super
|
2024
2100
|
end
|
2025
2101
|
|
2026
2102
|
# For multiple table support, PostgreSQL requires at least
|
@@ -663,7 +663,7 @@ module Sequel
|
|
663
663
|
|
664
664
|
# HAVING requires GROUP BY on SQLite
|
665
665
|
def having(*cond)
|
666
|
-
raise(InvalidOperation, "Can only specify a HAVING clause on a grouped dataset")
|
666
|
+
raise(InvalidOperation, "Can only specify a HAVING clause on a grouped dataset") if !@opts[:group] && db.sqlite_version < 33900
|
667
667
|
super
|
668
668
|
end
|
669
669
|
|
@@ -305,7 +305,7 @@ module Sequel
|
|
305
305
|
if USE_EXTENDED_RESULT_CODES
|
306
306
|
# Support SQLite exception codes if ruby-sqlite3 supports them.
|
307
307
|
def sqlite_error_code(exception)
|
308
|
-
exception.code if exception.
|
308
|
+
exception.code if defined?(exception.code)
|
309
309
|
end
|
310
310
|
end
|
311
311
|
end
|
data/lib/sequel/database/misc.rb
CHANGED
@@ -350,7 +350,7 @@ module Sequel
|
|
350
350
|
# strings with all whitespace, and ones that respond
|
351
351
|
# true to empty?
|
352
352
|
def blank_object?(obj)
|
353
|
-
return obj.blank? if obj.
|
353
|
+
return obj.blank? if defined?(obj.blank?)
|
354
354
|
case obj
|
355
355
|
when NilClass, FalseClass
|
356
356
|
true
|
@@ -359,7 +359,7 @@ module Sequel
|
|
359
359
|
when String
|
360
360
|
obj.strip.empty?
|
361
361
|
else
|
362
|
-
obj.
|
362
|
+
defined?(obj.empty?) ? obj.empty? : false
|
363
363
|
end
|
364
364
|
end
|
365
365
|
|
@@ -262,6 +262,7 @@ module Sequel
|
|
262
262
|
# operations on the table while the index is being
|
263
263
|
# built.
|
264
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.
|
265
266
|
# :opclass :: Set an opclass to use for all columns (per-column opclasses require
|
266
267
|
# custom SQL).
|
267
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"
|
@@ -1311,9 +1398,9 @@ module Sequel
|
|
1311
1398
|
# don't cache SQL for a dataset that uses this.
|
1312
1399
|
disable_sql_caching!
|
1313
1400
|
|
1314
|
-
if v.
|
1401
|
+
if defined?(v.sql_literal_append)
|
1315
1402
|
v.sql_literal_append(self, sql)
|
1316
|
-
elsif v.
|
1403
|
+
elsif defined?(v.sql_literal)
|
1317
1404
|
sql << v.sql_literal(self)
|
1318
1405
|
else
|
1319
1406
|
raise Error, "can't express #{v.inspect} as a SQL literal"
|