sequel 5.57.0 → 5.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +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"
|