Rename back unique keys to unique constraints
As we (I and @yahonda) talked about the naming in person, naming unique constraints as unique keys is very confusing to me. All documents and descriptions says it's unique constraints, but naming unique keys leads to misunderstanding it's a short-hand of unique indexes. Just naming it unique constraints is not misleading.
This commit is contained in:
parent
c32813ddc6
commit
b2790b6680
@ -503,7 +503,7 @@
|
|||||||
|
|
||||||
Because `deferrable: true` and `deferrable: :deferred` are hard to understand.
|
Because `deferrable: true` and `deferrable: :deferred` are hard to understand.
|
||||||
Both true and :deferred are truthy values.
|
Both true and :deferred are truthy values.
|
||||||
This behavior is the same as the deferrable option of the add_unique_key method, added in #46192.
|
This behavior is the same as the deferrable option of the add_unique_constraint method, added in #46192.
|
||||||
|
|
||||||
*Hiroyuki Ishii*
|
*Hiroyuki Ishii*
|
||||||
|
|
||||||
@ -720,8 +720,8 @@
|
|||||||
* Add support for unique constraints (PostgreSQL-only).
|
* Add support for unique constraints (PostgreSQL-only).
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
add_unique_key :sections, [:position], deferrable: :deferred, name: "unique_section_position"
|
add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_section_position"
|
||||||
remove_unique_key :sections, name: "unique_section_position"
|
remove_unique_constraint :sections, name: "unique_section_position"
|
||||||
```
|
```
|
||||||
|
|
||||||
See PostgreSQL's [Unique Constraints](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-UNIQUE-CONSTRAINTS) documentation for more on unique constraints.
|
See PostgreSQL's [Unique Constraints](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-UNIQUE-CONSTRAINTS) documentation for more on unique constraints.
|
||||||
@ -746,11 +746,11 @@
|
|||||||
Using the default behavior, the transaction would fail when executing the
|
Using the default behavior, the transaction would fail when executing the
|
||||||
first `UPDATE` statement.
|
first `UPDATE` statement.
|
||||||
|
|
||||||
By passing the `:deferrable` option to the `add_unique_key` statement in
|
By passing the `:deferrable` option to the `add_unique_constraint` statement in
|
||||||
migrations, it's possible to defer this check.
|
migrations, it's possible to defer this check.
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
add_unique_key :items, [:position], deferrable: :immediate
|
add_unique_constraint :items, [:position], deferrable: :immediate
|
||||||
```
|
```
|
||||||
|
|
||||||
Passing `deferrable: :immediate` does not change the behaviour of the previous example,
|
Passing `deferrable: :immediate` does not change the behaviour of the previous example,
|
||||||
@ -761,14 +761,14 @@
|
|||||||
check (after the statement), to a deferred check (after the transaction):
|
check (after the statement), to a deferred check (after the transaction):
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
add_unique_key :items, [:position], deferrable: :deferred
|
add_unique_constraint :items, [:position], deferrable: :deferred
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to change an existing unique index to deferrable, you can use :using_index
|
If you want to change an existing unique index to deferrable, you can use :using_index
|
||||||
to create deferrable unique constraints.
|
to create deferrable unique constraints.
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
add_unique_key :items, deferrable: :deferred, using_index: "index_items_on_position"
|
add_unique_constraint :items, deferrable: :deferred, using_index: "index_items_on_position"
|
||||||
```
|
```
|
||||||
|
|
||||||
*Hiroyuki Ishii*
|
*Hiroyuki Ishii*
|
||||||
|
@ -16,7 +16,7 @@ def accept(o)
|
|||||||
delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
|
delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
|
||||||
:options_include_default?, :supports_indexes_in_create?, :use_foreign_keys?,
|
:options_include_default?, :supports_indexes_in_create?, :use_foreign_keys?,
|
||||||
:quoted_columns_for_index, :supports_partial_index?, :supports_check_constraints?,
|
:quoted_columns_for_index, :supports_partial_index?, :supports_check_constraints?,
|
||||||
:supports_index_include?, :supports_exclusion_constraints?, :supports_unique_keys?,
|
:supports_index_include?, :supports_exclusion_constraints?, :supports_unique_constraints?,
|
||||||
:supports_nulls_not_distinct?,
|
:supports_nulls_not_distinct?,
|
||||||
to: :@conn, private: true
|
to: :@conn, private: true
|
||||||
|
|
||||||
@ -65,8 +65,8 @@ def visit_TableDefinition(o)
|
|||||||
statements.concat(o.exclusion_constraints.map { |exc| accept exc })
|
statements.concat(o.exclusion_constraints.map { |exc| accept exc })
|
||||||
end
|
end
|
||||||
|
|
||||||
if supports_unique_keys?
|
if supports_unique_constraints?
|
||||||
statements.concat(o.unique_keys.map { |exc| accept exc })
|
statements.concat(o.unique_constraints.map { |exc| accept exc })
|
||||||
end
|
end
|
||||||
|
|
||||||
create_sql << "(#{statements.join(', ')})" if statements.present?
|
create_sql << "(#{statements.join(', ')})" if statements.present?
|
||||||
|
@ -498,7 +498,7 @@ def supports_exclusion_constraints?
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Does this adapter support creating unique constraints?
|
# Does this adapter support creating unique constraints?
|
||||||
def supports_unique_keys?
|
def supports_unique_constraints?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -12,8 +12,8 @@ def visit_AlterTable(o)
|
|||||||
sql << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
|
sql << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
|
||||||
sql << o.exclusion_constraint_adds.map { |con| visit_AddExclusionConstraint con }.join(" ")
|
sql << o.exclusion_constraint_adds.map { |con| visit_AddExclusionConstraint con }.join(" ")
|
||||||
sql << o.exclusion_constraint_drops.map { |con| visit_DropExclusionConstraint con }.join(" ")
|
sql << o.exclusion_constraint_drops.map { |con| visit_DropExclusionConstraint con }.join(" ")
|
||||||
sql << o.unique_key_adds.map { |con| visit_AddUniqueKey con }.join(" ")
|
sql << o.unique_constraint_adds.map { |con| visit_AddUniqueConstraint con }.join(" ")
|
||||||
sql << o.unique_key_drops.map { |con| visit_DropUniqueKey con }.join(" ")
|
sql << o.unique_constraint_drops.map { |con| visit_DropUniqueConstraint con }.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
def visit_AddForeignKey(o)
|
def visit_AddForeignKey(o)
|
||||||
@ -49,7 +49,7 @@ def visit_ExclusionConstraintDefinition(o)
|
|||||||
sql.join(" ")
|
sql.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
def visit_UniqueKeyDefinition(o)
|
def visit_UniqueConstraintDefinition(o)
|
||||||
column_name = Array(o.column).map { |column| quote_column_name(column) }.join(", ")
|
column_name = Array(o.column).map { |column| quote_column_name(column) }.join(", ")
|
||||||
|
|
||||||
sql = ["CONSTRAINT"]
|
sql = ["CONSTRAINT"]
|
||||||
@ -77,11 +77,11 @@ def visit_DropExclusionConstraint(name)
|
|||||||
"DROP CONSTRAINT #{quote_column_name(name)}"
|
"DROP CONSTRAINT #{quote_column_name(name)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def visit_AddUniqueKey(o)
|
def visit_AddUniqueConstraint(o)
|
||||||
"ADD #{accept(o)}"
|
"ADD #{accept(o)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def visit_DropUniqueKey(name)
|
def visit_DropUniqueConstraint(name)
|
||||||
"DROP CONSTRAINT #{quote_column_name(name)}"
|
"DROP CONSTRAINT #{quote_column_name(name)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -211,7 +211,7 @@ def export_name_on_schema_dump?
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
UniqueKeyDefinition = Struct.new(:table_name, :column, :options) do
|
UniqueConstraintDefinition = Struct.new(:table_name, :column, :options) do
|
||||||
def name
|
def name
|
||||||
options[:name]
|
options[:name]
|
||||||
end
|
end
|
||||||
@ -239,12 +239,12 @@ def defined_for?(name: nil, column: nil, **options)
|
|||||||
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
||||||
include ColumnMethods
|
include ColumnMethods
|
||||||
|
|
||||||
attr_reader :exclusion_constraints, :unique_keys, :unlogged
|
attr_reader :exclusion_constraints, :unique_constraints, :unlogged
|
||||||
|
|
||||||
def initialize(*, **)
|
def initialize(*, **)
|
||||||
super
|
super
|
||||||
@exclusion_constraints = []
|
@exclusion_constraints = []
|
||||||
@unique_keys = []
|
@unique_constraints = []
|
||||||
@unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables
|
@unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -252,8 +252,8 @@ def exclusion_constraint(expression, **options)
|
|||||||
exclusion_constraints << new_exclusion_constraint_definition(expression, options)
|
exclusion_constraints << new_exclusion_constraint_definition(expression, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unique_key(column_name, **options)
|
def unique_constraint(column_name, **options)
|
||||||
unique_keys << new_unique_key_definition(column_name, options)
|
unique_constraints << new_unique_constraint_definition(column_name, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def new_exclusion_constraint_definition(expression, options) # :nodoc:
|
def new_exclusion_constraint_definition(expression, options) # :nodoc:
|
||||||
@ -261,9 +261,9 @@ def new_exclusion_constraint_definition(expression, options) # :nodoc:
|
|||||||
ExclusionConstraintDefinition.new(name, expression, options)
|
ExclusionConstraintDefinition.new(name, expression, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def new_unique_key_definition(column_name, options) # :nodoc:
|
def new_unique_constraint_definition(column_name, options) # :nodoc:
|
||||||
options = @conn.unique_key_options(name, column_name, options)
|
options = @conn.unique_constraint_options(name, column_name, options)
|
||||||
UniqueKeyDefinition.new(name, column_name, options)
|
UniqueConstraintDefinition.new(name, column_name, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def new_column_definition(name, type, **options) # :nodoc:
|
def new_column_definition(name, type, **options) # :nodoc:
|
||||||
@ -317,34 +317,34 @@ def remove_exclusion_constraint(*args)
|
|||||||
|
|
||||||
# Adds an unique constraint.
|
# Adds an unique constraint.
|
||||||
#
|
#
|
||||||
# t.unique_key(:position, name: 'unique_position', deferrable: :deferred)
|
# t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred)
|
||||||
#
|
#
|
||||||
# See {connection.add_unique_key}[rdoc-ref:SchemaStatements#add_unique_key]
|
# See {connection.add_unique_constraint}[rdoc-ref:SchemaStatements#add_unique_constraint]
|
||||||
def unique_key(*args)
|
def unique_constraint(*args)
|
||||||
@base.add_unique_key(name, *args)
|
@base.add_unique_constraint(name, *args)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Removes the given unique constraint from the table.
|
# Removes the given unique constraint from the table.
|
||||||
#
|
#
|
||||||
# t.remove_unique_key(name: "unique_position")
|
# t.remove_unique_constraint(name: "unique_position")
|
||||||
#
|
#
|
||||||
# See {connection.remove_unique_key}[rdoc-ref:SchemaStatements#remove_unique_key]
|
# See {connection.remove_unique_constraint}[rdoc-ref:SchemaStatements#remove_unique_constraint]
|
||||||
def remove_unique_key(*args)
|
def remove_unique_constraint(*args)
|
||||||
@base.remove_unique_key(name, *args)
|
@base.remove_unique_constraint(name, *args)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# = Active Record PostgreSQL Adapter Alter \Table
|
# = Active Record PostgreSQL Adapter Alter \Table
|
||||||
class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
|
class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
|
||||||
attr_reader :constraint_validations, :exclusion_constraint_adds, :exclusion_constraint_drops, :unique_key_adds, :unique_key_drops
|
attr_reader :constraint_validations, :exclusion_constraint_adds, :exclusion_constraint_drops, :unique_constraint_adds, :unique_constraint_drops
|
||||||
|
|
||||||
def initialize(td)
|
def initialize(td)
|
||||||
super
|
super
|
||||||
@constraint_validations = []
|
@constraint_validations = []
|
||||||
@exclusion_constraint_adds = []
|
@exclusion_constraint_adds = []
|
||||||
@exclusion_constraint_drops = []
|
@exclusion_constraint_drops = []
|
||||||
@unique_key_adds = []
|
@unique_constraint_adds = []
|
||||||
@unique_key_drops = []
|
@unique_constraint_drops = []
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_constraint(name)
|
def validate_constraint(name)
|
||||||
@ -359,12 +359,12 @@ def drop_exclusion_constraint(constraint_name)
|
|||||||
@exclusion_constraint_drops << constraint_name
|
@exclusion_constraint_drops << constraint_name
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_unique_key(column_name, options)
|
def add_unique_constraint(column_name, options)
|
||||||
@unique_key_adds << @td.new_unique_key_definition(column_name, options)
|
@unique_constraint_adds << @td.new_unique_constraint_definition(column_name, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def drop_unique_key(unique_key_name)
|
def drop_unique_constraint(unique_constraint_name)
|
||||||
@unique_key_drops << unique_key_name
|
@unique_constraint_drops << unique_constraint_name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -61,23 +61,23 @@ def exclusion_constraints_in_create(table, stream)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def unique_keys_in_create(table, stream)
|
def unique_constraints_in_create(table, stream)
|
||||||
if (unique_keys = @connection.unique_keys(table)).any?
|
if (unique_constraints = @connection.unique_constraints(table)).any?
|
||||||
add_unique_key_statements = unique_keys.map do |unique_key|
|
add_unique_constraint_statements = unique_constraints.map do |unique_constraint|
|
||||||
parts = [
|
parts = [
|
||||||
"t.unique_key #{unique_key.column.inspect}"
|
"t.unique_constraint #{unique_constraint.column.inspect}"
|
||||||
]
|
]
|
||||||
|
|
||||||
parts << "deferrable: #{unique_key.deferrable.inspect}" if unique_key.deferrable
|
parts << "deferrable: #{unique_constraint.deferrable.inspect}" if unique_constraint.deferrable
|
||||||
|
|
||||||
if unique_key.export_name_on_schema_dump?
|
if unique_constraint.export_name_on_schema_dump?
|
||||||
parts << "name: #{unique_key.name.inspect}"
|
parts << "name: #{unique_constraint.name.inspect}"
|
||||||
end
|
end
|
||||||
|
|
||||||
" #{parts.join(', ')}"
|
" #{parts.join(', ')}"
|
||||||
end
|
end
|
||||||
|
|
||||||
stream.puts add_unique_key_statements.sort.join("\n")
|
stream.puts add_unique_constraint_statements.sort.join("\n")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -640,8 +640,8 @@ def exclusion_constraints(table_name)
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Returns an array of unique constraints for the given table.
|
# Returns an array of unique constraints for the given table.
|
||||||
# The unique constraints are represented as UniqueKeyDefinition objects.
|
# The unique constraints are represented as UniqueConstraintDefinition objects.
|
||||||
def unique_keys(table_name)
|
def unique_constraints(table_name)
|
||||||
scope = quoted_scope(table_name)
|
scope = quoted_scope(table_name)
|
||||||
|
|
||||||
unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
||||||
@ -665,7 +665,7 @@ def unique_keys(table_name)
|
|||||||
deferrable: deferrable
|
deferrable: deferrable
|
||||||
}
|
}
|
||||||
|
|
||||||
UniqueKeyDefinition.new(table_name, columns, options)
|
UniqueConstraintDefinition.new(table_name, columns, options)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -717,7 +717,7 @@ def remove_exclusion_constraint(table_name, expression = nil, **options)
|
|||||||
|
|
||||||
# Adds a new unique constraint to the table.
|
# Adds a new unique constraint to the table.
|
||||||
#
|
#
|
||||||
# add_unique_key :sections, [:position], deferrable: :deferred, name: "unique_position"
|
# add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position"
|
||||||
#
|
#
|
||||||
# generates:
|
# generates:
|
||||||
#
|
#
|
||||||
@ -725,7 +725,7 @@ def remove_exclusion_constraint(table_name, expression = nil, **options)
|
|||||||
#
|
#
|
||||||
# If you want to change an existing unique index to deferrable, you can use :using_index to create deferrable unique constraints.
|
# If you want to change an existing unique index to deferrable, you can use :using_index to create deferrable unique constraints.
|
||||||
#
|
#
|
||||||
# add_unique_key :sections, deferrable: :deferred, name: "unique_position", using_index: "index_sections_on_position"
|
# add_unique_constraint :sections, deferrable: :deferred, name: "unique_position", using_index: "index_sections_on_position"
|
||||||
#
|
#
|
||||||
# The +options+ hash can include the following keys:
|
# The +options+ hash can include the following keys:
|
||||||
# [<tt>:name</tt>]
|
# [<tt>:name</tt>]
|
||||||
@ -734,15 +734,15 @@ def remove_exclusion_constraint(table_name, expression = nil, **options)
|
|||||||
# Specify whether or not the unique constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+.
|
# Specify whether or not the unique constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+.
|
||||||
# [<tt>:using_index</tt>]
|
# [<tt>:using_index</tt>]
|
||||||
# To specify an existing unique index name. Defaults to +nil+.
|
# To specify an existing unique index name. Defaults to +nil+.
|
||||||
def add_unique_key(table_name, column_name = nil, **options)
|
def add_unique_constraint(table_name, column_name = nil, **options)
|
||||||
options = unique_key_options(table_name, column_name, options)
|
options = unique_constraint_options(table_name, column_name, options)
|
||||||
at = create_alter_table(table_name)
|
at = create_alter_table(table_name)
|
||||||
at.add_unique_key(column_name, options)
|
at.add_unique_constraint(column_name, options)
|
||||||
|
|
||||||
execute schema_creation.accept(at)
|
execute schema_creation.accept(at)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unique_key_options(table_name, column_name, options) # :nodoc:
|
def unique_constraint_options(table_name, column_name, options) # :nodoc:
|
||||||
assert_valid_deferrable(options[:deferrable])
|
assert_valid_deferrable(options[:deferrable])
|
||||||
|
|
||||||
if column_name && options[:using_index]
|
if column_name && options[:using_index]
|
||||||
@ -750,22 +750,22 @@ def unique_key_options(table_name, column_name, options) # :nodoc:
|
|||||||
end
|
end
|
||||||
|
|
||||||
options = options.dup
|
options = options.dup
|
||||||
options[:name] ||= unique_key_name(table_name, column: column_name, **options)
|
options[:name] ||= unique_constraint_name(table_name, column: column_name, **options)
|
||||||
options
|
options
|
||||||
end
|
end
|
||||||
|
|
||||||
# Removes the given unique constraint from the table.
|
# Removes the given unique constraint from the table.
|
||||||
#
|
#
|
||||||
# remove_unique_key :sections, name: "unique_position"
|
# remove_unique_constraint :sections, name: "unique_position"
|
||||||
#
|
#
|
||||||
# The +column_name+ parameter will be ignored if present. It can be helpful
|
# The +column_name+ parameter will be ignored if present. It can be helpful
|
||||||
# to provide this in a migration's +change+ method so it can be reverted.
|
# to provide this in a migration's +change+ method so it can be reverted.
|
||||||
# In that case, +column_name+ will be used by #add_unique_key.
|
# In that case, +column_name+ will be used by #add_unique_constraint.
|
||||||
def remove_unique_key(table_name, column_name = nil, **options)
|
def remove_unique_constraint(table_name, column_name = nil, **options)
|
||||||
unique_name_to_delete = unique_key_for!(table_name, column: column_name, **options).name
|
unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name
|
||||||
|
|
||||||
at = create_alter_table(table_name)
|
at = create_alter_table(table_name)
|
||||||
at.drop_unique_key(unique_name_to_delete)
|
at.drop_unique_constraint(unique_name_to_delete)
|
||||||
|
|
||||||
execute schema_creation.accept(at)
|
execute schema_creation.accept(at)
|
||||||
end
|
end
|
||||||
@ -1038,7 +1038,7 @@ def exclusion_constraint_for!(table_name, expression: nil, **options)
|
|||||||
raise(ArgumentError, "Table '#{table_name}' has no exclusion constraint for #{expression || options}")
|
raise(ArgumentError, "Table '#{table_name}' has no exclusion constraint for #{expression || options}")
|
||||||
end
|
end
|
||||||
|
|
||||||
def unique_key_name(table_name, **options)
|
def unique_constraint_name(table_name, **options)
|
||||||
options.fetch(:name) do
|
options.fetch(:name) do
|
||||||
column_or_index = Array(options[:column] || options[:using_index]).map(&:to_s)
|
column_or_index = Array(options[:column] || options[:using_index]).map(&:to_s)
|
||||||
identifier = "#{table_name}_#{column_or_index * '_and_'}_unique"
|
identifier = "#{table_name}_#{column_or_index * '_and_'}_unique"
|
||||||
@ -1048,13 +1048,13 @@ def unique_key_name(table_name, **options)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def unique_key_for(table_name, **options)
|
def unique_constraint_for(table_name, **options)
|
||||||
name = unique_key_name(table_name, **options) unless options.key?(:column)
|
name = unique_constraint_name(table_name, **options) unless options.key?(:column)
|
||||||
unique_keys(table_name).detect { |unique_key| unique_key.defined_for?(name: name, **options) }
|
unique_constraints(table_name).detect { |unique_constraint| unique_constraint.defined_for?(name: name, **options) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def unique_key_for!(table_name, column: nil, **options)
|
def unique_constraint_for!(table_name, column: nil, **options)
|
||||||
unique_key_for(table_name, column: column, **options) ||
|
unique_constraint_for(table_name, column: column, **options) ||
|
||||||
raise(ArgumentError, "Table '#{table_name}' has no unique constraint for #{column || options}")
|
raise(ArgumentError, "Table '#{table_name}' has no unique constraint for #{column || options}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -226,7 +226,7 @@ def supports_exclusion_constraints?
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def supports_unique_keys?
|
def supports_unique_constraints?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ class Migration
|
|||||||
# * add_foreign_key
|
# * add_foreign_key
|
||||||
# * add_check_constraint
|
# * add_check_constraint
|
||||||
# * add_exclusion_constraint
|
# * add_exclusion_constraint
|
||||||
# * add_unique_key
|
# * add_unique_constraint
|
||||||
# * add_index
|
# * add_index
|
||||||
# * add_reference
|
# * add_reference
|
||||||
# * add_timestamps
|
# * add_timestamps
|
||||||
@ -33,7 +33,7 @@ class Migration
|
|||||||
# * remove_foreign_key (must supply a second table)
|
# * remove_foreign_key (must supply a second table)
|
||||||
# * remove_check_constraint
|
# * remove_check_constraint
|
||||||
# * remove_exclusion_constraint
|
# * remove_exclusion_constraint
|
||||||
# * remove_unique_key
|
# * remove_unique_constraint
|
||||||
# * remove_index
|
# * remove_index
|
||||||
# * remove_reference
|
# * remove_reference
|
||||||
# * remove_timestamps
|
# * remove_timestamps
|
||||||
@ -53,7 +53,7 @@ class CommandRecorder
|
|||||||
:change_column_comment, :change_table_comment,
|
:change_column_comment, :change_table_comment,
|
||||||
:add_check_constraint, :remove_check_constraint,
|
:add_check_constraint, :remove_check_constraint,
|
||||||
:add_exclusion_constraint, :remove_exclusion_constraint,
|
:add_exclusion_constraint, :remove_exclusion_constraint,
|
||||||
:add_unique_key, :remove_unique_key,
|
:add_unique_constraint, :remove_unique_constraint,
|
||||||
:create_enum, :drop_enum, :rename_enum, :add_enum_value, :rename_enum_value,
|
:create_enum, :drop_enum, :rename_enum, :add_enum_value, :rename_enum_value,
|
||||||
]
|
]
|
||||||
include JoinTable
|
include JoinTable
|
||||||
@ -161,7 +161,7 @@ module StraightReversions # :nodoc:
|
|||||||
add_foreign_key: :remove_foreign_key,
|
add_foreign_key: :remove_foreign_key,
|
||||||
add_check_constraint: :remove_check_constraint,
|
add_check_constraint: :remove_check_constraint,
|
||||||
add_exclusion_constraint: :remove_exclusion_constraint,
|
add_exclusion_constraint: :remove_exclusion_constraint,
|
||||||
add_unique_key: :remove_unique_key,
|
add_unique_constraint: :remove_unique_constraint,
|
||||||
enable_extension: :disable_extension,
|
enable_extension: :disable_extension,
|
||||||
create_enum: :drop_enum
|
create_enum: :drop_enum
|
||||||
}.each do |cmd, inv|
|
}.each do |cmd, inv|
|
||||||
@ -329,17 +329,17 @@ def invert_remove_exclusion_constraint(args)
|
|||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def invert_add_unique_key(args)
|
def invert_add_unique_constraint(args)
|
||||||
options = args.dup.extract_options!
|
options = args.dup.extract_options!
|
||||||
|
|
||||||
raise ActiveRecord::IrreversibleMigration, "add_unique_key is not reversible if given an using_index." if options[:using_index]
|
raise ActiveRecord::IrreversibleMigration, "add_unique_constraint is not reversible if given an using_index." if options[:using_index]
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def invert_remove_unique_key(args)
|
def invert_remove_unique_constraint(args)
|
||||||
_table, columns = args.dup.tap(&:extract_options!)
|
_table, columns = args.dup.tap(&:extract_options!)
|
||||||
|
|
||||||
raise ActiveRecord::IrreversibleMigration, "remove_unique_key is only reversible if given an column_name." if columns.blank?
|
raise ActiveRecord::IrreversibleMigration, "remove_unique_constraint is only reversible if given an column_name." if columns.blank?
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@ def table(table, stream)
|
|||||||
indexes_in_create(table, tbl)
|
indexes_in_create(table, tbl)
|
||||||
check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
|
check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
|
||||||
exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
|
exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
|
||||||
unique_keys_in_create(table, tbl) if @connection.supports_unique_keys?
|
unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
|
||||||
|
|
||||||
tbl.puts " end"
|
tbl.puts " end"
|
||||||
tbl.puts
|
tbl.puts
|
||||||
@ -229,10 +229,10 @@ def indexes_in_create(table, stream)
|
|||||||
indexes = indexes.reject { |index| exclusion_constraint_names.include?(index.name) }
|
indexes = indexes.reject { |index| exclusion_constraint_names.include?(index.name) }
|
||||||
end
|
end
|
||||||
|
|
||||||
if @connection.supports_unique_keys? && (unique_keys = @connection.unique_keys(table)).any?
|
if @connection.supports_unique_constraints? && (unique_constraints = @connection.unique_constraints(table)).any?
|
||||||
unique_key_names = unique_keys.collect(&:name)
|
unique_constraint_names = unique_constraints.collect(&:name)
|
||||||
|
|
||||||
indexes = indexes.reject { |index| unique_key_names.include?(index.name) }
|
indexes = indexes.reject { |index| unique_constraint_names.include?(index.name) }
|
||||||
end
|
end
|
||||||
|
|
||||||
index_statements = indexes.map do |index|
|
index_statements = indexes.map do |index|
|
||||||
|
@ -177,17 +177,17 @@ def test_remove_exclusion_constraint_removes_exclusion_constraint
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_unique_key_creates_unique_key
|
def test_unique_constraint_creates_unique_constraint
|
||||||
with_change_table do |t|
|
with_change_table do |t|
|
||||||
expect :add_unique_key, nil, [:delete_me, :foo, deferrable: :deferred, name: "unique_key"]
|
expect :add_unique_constraint, nil, [:delete_me, :foo, deferrable: :deferred, name: "unique_constraint"]
|
||||||
t.unique_key :foo, deferrable: :deferred, name: "unique_key"
|
t.unique_constraint :foo, deferrable: :deferred, name: "unique_constraint"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_remove_unique_key_removes_unique_key
|
def test_remove_unique_constraint_removes_unique_constraint
|
||||||
with_change_table do |t|
|
with_change_table do |t|
|
||||||
expect :remove_unique_key, nil, [:delete_me, name: "unique_key"]
|
expect :remove_unique_constraint, nil, [:delete_me, name: "unique_constraint"]
|
||||||
t.remove_unique_key name: "unique_key"
|
t.remove_unique_constraint name: "unique_constraint"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -486,25 +486,25 @@ def test_invert_remove_check_constraint_if_exists
|
|||||||
assert_equal [:add_check_constraint, [:dogs, "speed > 0", name: "speed_check", if_not_exists: true], nil], enable
|
assert_equal [:add_check_constraint, [:dogs, "speed > 0", name: "speed_check", if_not_exists: true], nil], enable
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_invert_add_unique_key_constraint_with_using_index
|
def test_invert_add_unique_constraint_constraint_with_using_index
|
||||||
assert_raises(ActiveRecord::IrreversibleMigration) do
|
assert_raises(ActiveRecord::IrreversibleMigration) do
|
||||||
@recorder.inverse_of :add_unique_key, [:dogs, using_index: "unique_index"]
|
@recorder.inverse_of :add_unique_constraint, [:dogs, using_index: "unique_index"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_invert_remove_unique_key_constraint
|
def test_invert_remove_unique_constraint_constraint
|
||||||
enable = @recorder.inverse_of :remove_unique_key, [:dogs, ["speed"], deferrable: :deferred, name: "uniq_speed"]
|
enable = @recorder.inverse_of :remove_unique_constraint, [:dogs, ["speed"], deferrable: :deferred, name: "uniq_speed"]
|
||||||
assert_equal [:add_unique_key, [:dogs, ["speed"], deferrable: :deferred, name: "uniq_speed"], nil], enable
|
assert_equal [:add_unique_constraint, [:dogs, ["speed"], deferrable: :deferred, name: "uniq_speed"], nil], enable
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_invert_remove_unique_key_constraint_without_options
|
def test_invert_remove_unique_constraint_constraint_without_options
|
||||||
enable = @recorder.inverse_of :remove_unique_key, [:dogs, ["speed"]]
|
enable = @recorder.inverse_of :remove_unique_constraint, [:dogs, ["speed"]]
|
||||||
assert_equal [:add_unique_key, [:dogs, ["speed"]], nil], enable
|
assert_equal [:add_unique_constraint, [:dogs, ["speed"]], nil], enable
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_invert_remove_unique_key_constraint_without_columns
|
def test_invert_remove_unique_constraint_constraint_without_columns
|
||||||
assert_raises(ActiveRecord::IrreversibleMigration) do
|
assert_raises(ActiveRecord::IrreversibleMigration) do
|
||||||
@recorder.inverse_of :remove_unique_key, [:dogs, name: "uniq_speed"]
|
@recorder.inverse_of :remove_unique_constraint, [:dogs, name: "uniq_speed"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
220
activerecord/test/cases/migration/unique_constraint_test.rb
Normal file
220
activerecord/test/cases/migration/unique_constraint_test.rb
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "cases/helper"
|
||||||
|
require "support/schema_dumping_helper"
|
||||||
|
|
||||||
|
if ActiveRecord::Base.connection.supports_unique_constraints?
|
||||||
|
module ActiveRecord
|
||||||
|
class Migration
|
||||||
|
class UniqueConstraintTest < ActiveRecord::TestCase
|
||||||
|
include SchemaDumpingHelper
|
||||||
|
|
||||||
|
class Section < ActiveRecord::Base
|
||||||
|
end
|
||||||
|
|
||||||
|
setup do
|
||||||
|
@connection = ActiveRecord::Base.connection
|
||||||
|
@connection.create_table "sections", force: true do |t|
|
||||||
|
t.integer "position", null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
teardown do
|
||||||
|
@connection.drop_table "sections", if_exists: true
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_unique_constraints
|
||||||
|
unique_constraints = @connection.unique_constraints("test_unique_constraints")
|
||||||
|
|
||||||
|
expected_constraints = [
|
||||||
|
{
|
||||||
|
name: "test_unique_constraints_position_deferrable_false",
|
||||||
|
deferrable: false,
|
||||||
|
column: ["position_1"]
|
||||||
|
}, {
|
||||||
|
name: "test_unique_constraints_position_deferrable_immediate",
|
||||||
|
deferrable: :immediate,
|
||||||
|
column: ["position_2"]
|
||||||
|
}, {
|
||||||
|
name: "test_unique_constraints_position_deferrable_deferred",
|
||||||
|
deferrable: :deferred,
|
||||||
|
column: ["position_3"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
assert_equal expected_constraints.size, unique_constraints.size
|
||||||
|
|
||||||
|
expected_constraints.each do |expected_constraint|
|
||||||
|
constraint = unique_constraints.find { |constraint| constraint.name == expected_constraint[:name] }
|
||||||
|
assert_equal "test_unique_constraints", constraint.table_name
|
||||||
|
assert_equal expected_constraint[:name], constraint.name
|
||||||
|
assert_equal expected_constraint[:column], constraint.column
|
||||||
|
assert_equal expected_constraint[:deferrable], constraint.deferrable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_unique_constraints_scoped_to_schemas
|
||||||
|
@connection.add_unique_constraint :sections, [:position]
|
||||||
|
|
||||||
|
assert_no_changes -> { @connection.unique_constraints("sections").size } do
|
||||||
|
@connection.create_schema "test_schema"
|
||||||
|
@connection.create_table "test_schema.sections" do |t|
|
||||||
|
t.integer :position
|
||||||
|
end
|
||||||
|
@connection.add_unique_constraint "test_schema.sections", [:position]
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
@connection.drop_schema "test_schema"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_unique_constraint_without_deferrable
|
||||||
|
@connection.add_unique_constraint :sections, [:position]
|
||||||
|
|
||||||
|
unique_constraints = @connection.unique_constraints("sections")
|
||||||
|
assert_equal 1, unique_constraints.size
|
||||||
|
|
||||||
|
constraint = unique_constraints.first
|
||||||
|
assert_equal "sections", constraint.table_name
|
||||||
|
assert_equal "uniq_rails_1e07660b77", constraint.name
|
||||||
|
assert_equal false, constraint.deferrable
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_unique_constraint_with_deferrable_false
|
||||||
|
@connection.add_unique_constraint :sections, [:position], deferrable: false
|
||||||
|
|
||||||
|
unique_constraints = @connection.unique_constraints("sections")
|
||||||
|
assert_equal 1, unique_constraints.size
|
||||||
|
|
||||||
|
constraint = unique_constraints.first
|
||||||
|
assert_equal "sections", constraint.table_name
|
||||||
|
assert_equal "uniq_rails_1e07660b77", constraint.name
|
||||||
|
assert_equal false, constraint.deferrable
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_unique_constraint_with_deferrable_immediate
|
||||||
|
@connection.add_unique_constraint :sections, [:position], deferrable: :immediate
|
||||||
|
|
||||||
|
unique_constraints = @connection.unique_constraints("sections")
|
||||||
|
assert_equal 1, unique_constraints.size
|
||||||
|
|
||||||
|
constraint = unique_constraints.first
|
||||||
|
assert_equal "sections", constraint.table_name
|
||||||
|
assert_equal "uniq_rails_1e07660b77", constraint.name
|
||||||
|
assert_equal :immediate, constraint.deferrable
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_unique_constraint_with_deferrable_deferred
|
||||||
|
@connection.add_unique_constraint :sections, [:position], deferrable: :deferred
|
||||||
|
|
||||||
|
unique_constraints = @connection.unique_constraints("sections")
|
||||||
|
assert_equal 1, unique_constraints.size
|
||||||
|
|
||||||
|
constraint = unique_constraints.first
|
||||||
|
assert_equal "sections", constraint.table_name
|
||||||
|
assert_equal "uniq_rails_1e07660b77", constraint.name
|
||||||
|
assert_equal :deferred, constraint.deferrable
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_unique_constraint_with_deferrable_invalid
|
||||||
|
error = assert_raises(ArgumentError) do
|
||||||
|
@connection.add_unique_constraint :sections, [:position], deferrable: true
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal "deferrable must be `:immediate` or `:deferred`, got: `true`", error.message
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_added_deferrable_initially_immediate_unique_constraint
|
||||||
|
@connection.add_unique_constraint :sections, [:position], deferrable: :immediate, name: "unique_section_position"
|
||||||
|
|
||||||
|
section = Section.create!(position: 1)
|
||||||
|
|
||||||
|
assert_raises(ActiveRecord::StatementInvalid) do
|
||||||
|
Section.transaction(requires_new: true) do
|
||||||
|
Section.create!(position: 1)
|
||||||
|
section.update!(position: 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_nothing_raised do
|
||||||
|
Section.transaction(requires_new: true) do
|
||||||
|
Section.connection.exec_query("SET CONSTRAINTS unique_section_position DEFERRED")
|
||||||
|
Section.create!(position: 1)
|
||||||
|
section.update!(position: 2)
|
||||||
|
|
||||||
|
# NOTE: Clear `SET CONSTRAINTS` statement at the end of transaction.
|
||||||
|
raise ActiveRecord::Rollback
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_unique_constraint_with_name_and_using_index
|
||||||
|
@connection.add_index :sections, [:position], name: "unique_index", unique: true
|
||||||
|
@connection.add_unique_constraint :sections, name: "unique_constraint", deferrable: :immediate, using_index: "unique_index"
|
||||||
|
|
||||||
|
unique_constraints = @connection.unique_constraints("sections")
|
||||||
|
assert_equal 1, unique_constraints.size
|
||||||
|
|
||||||
|
constraint = unique_constraints.first
|
||||||
|
assert_equal "sections", constraint.table_name
|
||||||
|
assert_equal "unique_constraint", constraint.name
|
||||||
|
assert_equal ["position"], constraint.column
|
||||||
|
assert_equal :immediate, constraint.deferrable
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_unique_constraint_with_only_using_index
|
||||||
|
@connection.add_index :sections, [:position], name: "unique_index", unique: true
|
||||||
|
@connection.add_unique_constraint :sections, using_index: "unique_index"
|
||||||
|
|
||||||
|
unique_constraints = @connection.unique_constraints("sections")
|
||||||
|
assert_equal 1, unique_constraints.size
|
||||||
|
|
||||||
|
constraint = unique_constraints.first
|
||||||
|
assert_equal "sections", constraint.table_name
|
||||||
|
assert_equal "uniq_rails_79b901ffb4", constraint.name
|
||||||
|
assert_equal ["position"], constraint.column
|
||||||
|
assert_equal false, constraint.deferrable
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_unique_constraint_with_columns_and_using_index
|
||||||
|
@connection.add_index :sections, [:position], name: "unique_index", unique: true
|
||||||
|
|
||||||
|
assert_raises(ArgumentError) do
|
||||||
|
@connection.add_unique_constraint :sections, [:position], using_index: "unique_index"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_remove_unique_constraint
|
||||||
|
@connection.add_unique_constraint :sections, [:position], name: :unique_section_position
|
||||||
|
assert_equal 1, @connection.unique_constraints("sections").size
|
||||||
|
@connection.remove_unique_constraint :sections, name: :unique_section_position
|
||||||
|
assert_empty @connection.unique_constraints("sections")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_remove_unique_constraint_by_column
|
||||||
|
@connection.add_unique_constraint :sections, [:position]
|
||||||
|
assert_equal 1, @connection.unique_constraints("sections").size
|
||||||
|
@connection.remove_unique_constraint :sections, [:position]
|
||||||
|
assert_empty @connection.unique_constraints("sections")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_remove_non_existing_unique_constraint
|
||||||
|
assert_raises(ArgumentError, match: /Table 'sections' has no unique constraint/) do
|
||||||
|
@connection.remove_unique_constraint :sections, name: "nonexistent"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_renamed_unique_constraint
|
||||||
|
@connection.add_unique_constraint :sections, [:position]
|
||||||
|
@connection.rename_column :sections, :position, :new_position
|
||||||
|
|
||||||
|
unique_constraints = @connection.unique_constraints("sections")
|
||||||
|
assert_equal 1, unique_constraints.size
|
||||||
|
|
||||||
|
constraint = unique_constraints.first
|
||||||
|
assert_equal "sections", constraint.table_name
|
||||||
|
assert_equal ["new_position"], constraint.column
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,220 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "cases/helper"
|
|
||||||
require "support/schema_dumping_helper"
|
|
||||||
|
|
||||||
if ActiveRecord::Base.connection.supports_unique_keys?
|
|
||||||
module ActiveRecord
|
|
||||||
class Migration
|
|
||||||
class UniqueKeyTest < ActiveRecord::TestCase
|
|
||||||
include SchemaDumpingHelper
|
|
||||||
|
|
||||||
class Section < ActiveRecord::Base
|
|
||||||
end
|
|
||||||
|
|
||||||
setup do
|
|
||||||
@connection = ActiveRecord::Base.connection
|
|
||||||
@connection.create_table "sections", force: true do |t|
|
|
||||||
t.integer "position", null: false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
teardown do
|
|
||||||
@connection.drop_table "sections", if_exists: true
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unique_keys
|
|
||||||
unique_keys = @connection.unique_keys("test_unique_keys")
|
|
||||||
|
|
||||||
expected_constraints = [
|
|
||||||
{
|
|
||||||
name: "test_unique_keys_position_deferrable_false",
|
|
||||||
deferrable: false,
|
|
||||||
column: ["position_1"]
|
|
||||||
}, {
|
|
||||||
name: "test_unique_keys_position_deferrable_immediate",
|
|
||||||
deferrable: :immediate,
|
|
||||||
column: ["position_2"]
|
|
||||||
}, {
|
|
||||||
name: "test_unique_keys_position_deferrable_deferred",
|
|
||||||
deferrable: :deferred,
|
|
||||||
column: ["position_3"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
assert_equal expected_constraints.size, unique_keys.size
|
|
||||||
|
|
||||||
expected_constraints.each do |expected_constraint|
|
|
||||||
constraint = unique_keys.find { |constraint| constraint.name == expected_constraint[:name] }
|
|
||||||
assert_equal "test_unique_keys", constraint.table_name
|
|
||||||
assert_equal expected_constraint[:name], constraint.name
|
|
||||||
assert_equal expected_constraint[:column], constraint.column
|
|
||||||
assert_equal expected_constraint[:deferrable], constraint.deferrable
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unique_keys_scoped_to_schemas
|
|
||||||
@connection.add_unique_key :sections, [:position]
|
|
||||||
|
|
||||||
assert_no_changes -> { @connection.unique_keys("sections").size } do
|
|
||||||
@connection.create_schema "test_schema"
|
|
||||||
@connection.create_table "test_schema.sections" do |t|
|
|
||||||
t.integer :position
|
|
||||||
end
|
|
||||||
@connection.add_unique_key "test_schema.sections", [:position]
|
|
||||||
end
|
|
||||||
ensure
|
|
||||||
@connection.drop_schema "test_schema"
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_add_unique_key_without_deferrable
|
|
||||||
@connection.add_unique_key :sections, [:position]
|
|
||||||
|
|
||||||
unique_keys = @connection.unique_keys("sections")
|
|
||||||
assert_equal 1, unique_keys.size
|
|
||||||
|
|
||||||
constraint = unique_keys.first
|
|
||||||
assert_equal "sections", constraint.table_name
|
|
||||||
assert_equal "uniq_rails_1e07660b77", constraint.name
|
|
||||||
assert_equal false, constraint.deferrable
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_add_unique_key_with_deferrable_false
|
|
||||||
@connection.add_unique_key :sections, [:position], deferrable: false
|
|
||||||
|
|
||||||
unique_keys = @connection.unique_keys("sections")
|
|
||||||
assert_equal 1, unique_keys.size
|
|
||||||
|
|
||||||
constraint = unique_keys.first
|
|
||||||
assert_equal "sections", constraint.table_name
|
|
||||||
assert_equal "uniq_rails_1e07660b77", constraint.name
|
|
||||||
assert_equal false, constraint.deferrable
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_add_unique_key_with_deferrable_immediate
|
|
||||||
@connection.add_unique_key :sections, [:position], deferrable: :immediate
|
|
||||||
|
|
||||||
unique_keys = @connection.unique_keys("sections")
|
|
||||||
assert_equal 1, unique_keys.size
|
|
||||||
|
|
||||||
constraint = unique_keys.first
|
|
||||||
assert_equal "sections", constraint.table_name
|
|
||||||
assert_equal "uniq_rails_1e07660b77", constraint.name
|
|
||||||
assert_equal :immediate, constraint.deferrable
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_add_unique_key_with_deferrable_deferred
|
|
||||||
@connection.add_unique_key :sections, [:position], deferrable: :deferred
|
|
||||||
|
|
||||||
unique_keys = @connection.unique_keys("sections")
|
|
||||||
assert_equal 1, unique_keys.size
|
|
||||||
|
|
||||||
constraint = unique_keys.first
|
|
||||||
assert_equal "sections", constraint.table_name
|
|
||||||
assert_equal "uniq_rails_1e07660b77", constraint.name
|
|
||||||
assert_equal :deferred, constraint.deferrable
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_add_unique_key_with_deferrable_invalid
|
|
||||||
error = assert_raises(ArgumentError) do
|
|
||||||
@connection.add_unique_key :sections, [:position], deferrable: true
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_equal "deferrable must be `:immediate` or `:deferred`, got: `true`", error.message
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_added_deferrable_initially_immediate_unique_key
|
|
||||||
@connection.add_unique_key :sections, [:position], deferrable: :immediate, name: "unique_section_position"
|
|
||||||
|
|
||||||
section = Section.create!(position: 1)
|
|
||||||
|
|
||||||
assert_raises(ActiveRecord::StatementInvalid) do
|
|
||||||
Section.transaction(requires_new: true) do
|
|
||||||
Section.create!(position: 1)
|
|
||||||
section.update!(position: 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_nothing_raised do
|
|
||||||
Section.transaction(requires_new: true) do
|
|
||||||
Section.connection.exec_query("SET CONSTRAINTS unique_section_position DEFERRED")
|
|
||||||
Section.create!(position: 1)
|
|
||||||
section.update!(position: 2)
|
|
||||||
|
|
||||||
# NOTE: Clear `SET CONSTRAINTS` statement at the end of transaction.
|
|
||||||
raise ActiveRecord::Rollback
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_add_unique_key_with_name_and_using_index
|
|
||||||
@connection.add_index :sections, [:position], name: "unique_index", unique: true
|
|
||||||
@connection.add_unique_key :sections, name: "unique_constraint", deferrable: :immediate, using_index: "unique_index"
|
|
||||||
|
|
||||||
unique_keys = @connection.unique_keys("sections")
|
|
||||||
assert_equal 1, unique_keys.size
|
|
||||||
|
|
||||||
constraint = unique_keys.first
|
|
||||||
assert_equal "sections", constraint.table_name
|
|
||||||
assert_equal "unique_constraint", constraint.name
|
|
||||||
assert_equal ["position"], constraint.column
|
|
||||||
assert_equal :immediate, constraint.deferrable
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_add_unique_key_with_only_using_index
|
|
||||||
@connection.add_index :sections, [:position], name: "unique_index", unique: true
|
|
||||||
@connection.add_unique_key :sections, using_index: "unique_index"
|
|
||||||
|
|
||||||
unique_keys = @connection.unique_keys("sections")
|
|
||||||
assert_equal 1, unique_keys.size
|
|
||||||
|
|
||||||
constraint = unique_keys.first
|
|
||||||
assert_equal "sections", constraint.table_name
|
|
||||||
assert_equal "uniq_rails_79b901ffb4", constraint.name
|
|
||||||
assert_equal ["position"], constraint.column
|
|
||||||
assert_equal false, constraint.deferrable
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_add_unique_key_with_columns_and_using_index
|
|
||||||
@connection.add_index :sections, [:position], name: "unique_index", unique: true
|
|
||||||
|
|
||||||
assert_raises(ArgumentError) do
|
|
||||||
@connection.add_unique_key :sections, [:position], using_index: "unique_index"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_remove_unique_key
|
|
||||||
@connection.add_unique_key :sections, [:position], name: :unique_section_position
|
|
||||||
assert_equal 1, @connection.unique_keys("sections").size
|
|
||||||
@connection.remove_unique_key :sections, name: :unique_section_position
|
|
||||||
assert_empty @connection.unique_keys("sections")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_remove_unique_key_by_column
|
|
||||||
@connection.add_unique_key :sections, [:position]
|
|
||||||
assert_equal 1, @connection.unique_keys("sections").size
|
|
||||||
@connection.remove_unique_key :sections, [:position]
|
|
||||||
assert_empty @connection.unique_keys("sections")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_remove_non_existing_unique_key
|
|
||||||
assert_raises(ArgumentError, match: /Table 'sections' has no unique constraint/) do
|
|
||||||
@connection.remove_unique_key :sections, name: "nonexistent"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_renamed_unique_key
|
|
||||||
@connection.add_unique_key :sections, [:position]
|
|
||||||
@connection.rename_column :sections, :position, :new_position
|
|
||||||
|
|
||||||
unique_keys = @connection.unique_keys("sections")
|
|
||||||
assert_equal 1, unique_keys.size
|
|
||||||
|
|
||||||
constraint = unique_keys.first
|
|
||||||
assert_equal "sections", constraint.table_name
|
|
||||||
assert_equal ["new_position"], constraint.column
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -241,19 +241,19 @@ def test_schema_dumps_exclusion_constraints
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if ActiveRecord::Base.connection.supports_unique_keys?
|
if ActiveRecord::Base.connection.supports_unique_constraints?
|
||||||
def test_schema_dumps_unique_keys
|
def test_schema_dumps_unique_constraints
|
||||||
output = dump_table_schema("test_unique_keys")
|
output = dump_table_schema("test_unique_constraints")
|
||||||
constraint_definitions = output.split(/\n/).grep(/t\.unique_key/)
|
constraint_definitions = output.split(/\n/).grep(/t\.unique_constraint/)
|
||||||
|
|
||||||
assert_equal 3, constraint_definitions.size
|
assert_equal 3, constraint_definitions.size
|
||||||
assert_match 't.unique_key ["position_1"], name: "test_unique_keys_position_deferrable_false"', output
|
assert_match 't.unique_constraint ["position_1"], name: "test_unique_constraints_position_deferrable_false"', output
|
||||||
assert_match 't.unique_key ["position_2"], deferrable: :immediate, name: "test_unique_keys_position_deferrable_immediate"', output
|
assert_match 't.unique_constraint ["position_2"], deferrable: :immediate, name: "test_unique_constraints_position_deferrable_immediate"', output
|
||||||
assert_match 't.unique_key ["position_3"], deferrable: :deferred, name: "test_unique_keys_position_deferrable_deferred"', output
|
assert_match 't.unique_constraint ["position_3"], deferrable: :deferred, name: "test_unique_constraints_position_deferrable_deferred"', output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_schema_does_not_dumps_unique_key_indexes
|
def test_schema_does_not_dump_unique_constraints_as_indexes
|
||||||
output = dump_table_schema("test_unique_keys")
|
output = dump_table_schema("test_unique_constraints")
|
||||||
unique_index_definitions = output.split(/\n/).grep(/t\.index.*unique: true/)
|
unique_index_definitions = output.split(/\n/).grep(/t\.index.*unique: true/)
|
||||||
|
|
||||||
assert_equal 0, unique_index_definitions.size
|
assert_equal 0, unique_index_definitions.size
|
||||||
|
@ -153,14 +153,14 @@
|
|||||||
t.exclusion_constraint "daterange(transaction_from, transaction_to) WITH &&", using: :gist, where: "transaction_from IS NOT NULL AND transaction_to IS NOT NULL", name: "test_exclusion_constraints_transaction_overlap", deferrable: :deferred
|
t.exclusion_constraint "daterange(transaction_from, transaction_to) WITH &&", using: :gist, where: "transaction_from IS NOT NULL AND transaction_to IS NOT NULL", name: "test_exclusion_constraints_transaction_overlap", deferrable: :deferred
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :test_unique_keys, force: true do |t|
|
create_table :test_unique_constraints, force: true do |t|
|
||||||
t.integer :position_1
|
t.integer :position_1
|
||||||
t.integer :position_2
|
t.integer :position_2
|
||||||
t.integer :position_3
|
t.integer :position_3
|
||||||
|
|
||||||
t.unique_key :position_1, name: "test_unique_keys_position_deferrable_false"
|
t.unique_constraint :position_1, name: "test_unique_constraints_position_deferrable_false"
|
||||||
t.unique_key :position_2, name: "test_unique_keys_position_deferrable_immediate", deferrable: :immediate
|
t.unique_constraint :position_2, name: "test_unique_constraints_position_deferrable_immediate", deferrable: :immediate
|
||||||
t.unique_key :position_3, name: "test_unique_keys_position_deferrable_deferred", deferrable: :deferred
|
t.unique_constraint :position_3, name: "test_unique_constraints_position_deferrable_deferred", deferrable: :deferred
|
||||||
end
|
end
|
||||||
|
|
||||||
if supports_partitioned_indexes?
|
if supports_partitioned_indexes?
|
||||||
|
@ -654,14 +654,14 @@ Unique Constraint
|
|||||||
# db/migrate/20230422225213_create_items.rb
|
# db/migrate/20230422225213_create_items.rb
|
||||||
create_table :items do |t|
|
create_table :items do |t|
|
||||||
t.integer :position, null: false
|
t.integer :position, null: false
|
||||||
t.unique_key [:position], deferrable: :immediate
|
t.unique_constraint [:position], deferrable: :immediate
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to change an existing unique index to deferrable, you can use `:using_index` to create deferrable unique constraints.
|
If you want to change an existing unique index to deferrable, you can use `:using_index` to create deferrable unique constraints.
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
add_unique_key :items, deferrable: :deferred, using_index: "index_items_on_position"
|
add_unique_constraint :items, deferrable: :deferred, using_index: "index_items_on_position"
|
||||||
```
|
```
|
||||||
|
|
||||||
Like foreign keys, unique constraints can be deferred by setting `:deferrable` to either `:immediate` or `:deferred`. By default, `:deferrable` is `false` and the constraint is always checked immediately.
|
Like foreign keys, unique constraints can be deferred by setting `:deferrable` to either `:immediate` or `:deferred`. By default, `:deferrable` is `false` and the constraint is always checked immediately.
|
||||||
|
Loading…
Reference in New Issue
Block a user