mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
add support for NOT VALID
check constraints in PostgreSQL
Active Record already supports adding `NOT VALID` foreign key constraints in PostgreSQL, but back in #31323 when check constraint support was initially added, the ability to add a check constraint and validate it separately was not included. This adds that functionality!
This commit is contained in:
parent
580eefe1bc
commit
368da3ef29
6 changed files with 71 additions and 2 deletions
|
@ -1,3 +1,7 @@
|
||||||
|
* Add support for check constraints that are `NOT VALID` via `validate: false` (PostgreSQL-only).
|
||||||
|
|
||||||
|
*Alex Robbin*
|
||||||
|
|
||||||
* Ensure the default configuration is considered primary or first for an environment
|
* Ensure the default configuration is considered primary or first for an environment
|
||||||
|
|
||||||
If a multiple database application provides a configuration named primary, that will be treated as default. In applications that do not have a primary entry, the default database configuration will be the first configuration for an environment.
|
If a multiple database application provides a configuration named primary, that will be treated as default. In applications that do not have a primary entry, the default database configuration will be the first configuration for an environment.
|
||||||
|
|
|
@ -132,6 +132,11 @@ module ActiveRecord
|
||||||
options[:name]
|
options[:name]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate?
|
||||||
|
options.fetch(:validate, true)
|
||||||
|
end
|
||||||
|
alias validated? validate?
|
||||||
|
|
||||||
def export_name_on_schema_dump?
|
def export_name_on_schema_dump?
|
||||||
!ActiveRecord::SchemaDumper.chk_ignore_pattern.match?(name) if name
|
!ActiveRecord::SchemaDumper.chk_ignore_pattern.match?(name) if name
|
||||||
end
|
end
|
||||||
|
|
|
@ -1142,6 +1142,11 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# ALTER TABLE "products" ADD CONSTRAINT price_check CHECK (price > 0)
|
# ALTER TABLE "products" ADD CONSTRAINT price_check CHECK (price > 0)
|
||||||
#
|
#
|
||||||
|
# The +options+ hash can include the following keys:
|
||||||
|
# [<tt>:name</tt>]
|
||||||
|
# The constraint name. Defaults to <tt>chk_rails_<identifier></tt>.
|
||||||
|
# [<tt>:validate</tt>]
|
||||||
|
# (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
|
||||||
def add_check_constraint(table_name, expression, **options)
|
def add_check_constraint(table_name, expression, **options)
|
||||||
return unless supports_check_constraints?
|
return unless supports_check_constraints?
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,10 @@ module ActiveRecord
|
||||||
super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
|
super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def visit_CheckConstraintDefinition(o)
|
||||||
|
super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
|
||||||
|
end
|
||||||
|
|
||||||
def visit_ValidateConstraint(name)
|
def visit_ValidateConstraint(name)
|
||||||
"VALIDATE CONSTRAINT #{quote_column_name(name)}"
|
"VALIDATE CONSTRAINT #{quote_column_name(name)}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -523,7 +523,7 @@ module ActiveRecord
|
||||||
scope = quoted_scope(table_name)
|
scope = quoted_scope(table_name)
|
||||||
|
|
||||||
check_info = exec_query(<<-SQL, "SCHEMA")
|
check_info = exec_query(<<-SQL, "SCHEMA")
|
||||||
SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef
|
SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.convalidated AS valid
|
||||||
FROM pg_constraint c
|
FROM pg_constraint c
|
||||||
JOIN pg_class t ON c.conrelid = t.oid
|
JOIN pg_class t ON c.conrelid = t.oid
|
||||||
WHERE c.contype = 'c'
|
WHERE c.contype = 'c'
|
||||||
|
@ -532,7 +532,8 @@ module ActiveRecord
|
||||||
|
|
||||||
check_info.map do |row|
|
check_info.map do |row|
|
||||||
options = {
|
options = {
|
||||||
name: row["conname"]
|
name: row["conname"],
|
||||||
|
validate: row["valid"]
|
||||||
}
|
}
|
||||||
expression = row["constraintdef"][/CHECK \({2}(.+)\){2}/, 1]
|
expression = row["constraintdef"][/CHECK \({2}(.+)\){2}/, 1]
|
||||||
|
|
||||||
|
@ -632,6 +633,19 @@ module ActiveRecord
|
||||||
validate_constraint from_table, fk_name_to_validate
|
validate_constraint from_table, fk_name_to_validate
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Validates the given check constraint.
|
||||||
|
#
|
||||||
|
# validate_check_constraint :products, name: "price_check"
|
||||||
|
#
|
||||||
|
# The +options+ hash accepts the same keys as add_check_constraint[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
|
||||||
|
def validate_check_constraint(table_name, **options)
|
||||||
|
return unless supports_validate_constraints?
|
||||||
|
|
||||||
|
chk_name_to_validate = check_constraint_for!(table_name, **options).name
|
||||||
|
|
||||||
|
validate_constraint table_name, chk_name_to_validate
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def schema_creation
|
def schema_creation
|
||||||
PostgreSQL::SchemaCreation.new(self)
|
PostgreSQL::SchemaCreation.new(self)
|
||||||
|
|
|
@ -64,6 +64,43 @@ if ActiveRecord::Base.connection.supports_check_constraints?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if ActiveRecord::Base.connection.supports_validate_constraints?
|
||||||
|
def test_not_valid_check_constraint
|
||||||
|
Trade.create(quantity: -1)
|
||||||
|
|
||||||
|
@connection.add_check_constraint :trades, "quantity > 0", name: "quantity_check", validate: false
|
||||||
|
|
||||||
|
assert_raises(ActiveRecord::StatementInvalid) do
|
||||||
|
Trade.create(quantity: -1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_validate_check_constraint_by_name
|
||||||
|
@connection.add_check_constraint :trades, "quantity > 0", name: "quantity_check", validate: false
|
||||||
|
assert_not_predicate @connection.check_constraints("trades").first, :validated?
|
||||||
|
|
||||||
|
@connection.validate_check_constraint :trades, name: "quantity_check"
|
||||||
|
assert_predicate @connection.check_constraints("trades").first, :validated?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_validate_non_existing_check_constraint_raises
|
||||||
|
assert_raises ArgumentError do
|
||||||
|
@connection.validate_check_constraint :trades, name: "quantity_check"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# Check constraint should still be created, but should not be invalid
|
||||||
|
def test_add_invalid_check_constraint
|
||||||
|
@connection.add_check_constraint :trades, "quantity > 0", name: "quantity_check", validate: false
|
||||||
|
|
||||||
|
check_constraints = @connection.check_constraints("trades")
|
||||||
|
assert_equal 1, check_constraints.size
|
||||||
|
|
||||||
|
cc = check_constraints.first
|
||||||
|
assert_predicate cc, :validated?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_remove_check_constraint
|
def test_remove_check_constraint
|
||||||
@connection.add_check_constraint :trades, "price > 0", name: "price_check"
|
@connection.add_check_constraint :trades, "price > 0", name: "price_check"
|
||||||
@connection.add_check_constraint :trades, "quantity > 0", name: "quantity_check"
|
@connection.add_check_constraint :trades, "quantity > 0", name: "quantity_check"
|
||||||
|
|
Loading…
Reference in a new issue