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
|
||||
|
||||
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]
|
||||
end
|
||||
|
||||
def validate?
|
||||
options.fetch(:validate, true)
|
||||
end
|
||||
alias validated? validate?
|
||||
|
||||
def export_name_on_schema_dump?
|
||||
!ActiveRecord::SchemaDumper.chk_ignore_pattern.match?(name) if name
|
||||
end
|
||||
|
|
|
@ -1142,6 +1142,11 @@ module ActiveRecord
|
|||
#
|
||||
# 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)
|
||||
return unless supports_check_constraints?
|
||||
|
||||
|
|
|
@ -13,6 +13,10 @@ module ActiveRecord
|
|||
super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
|
||||
end
|
||||
|
||||
def visit_CheckConstraintDefinition(o)
|
||||
super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
|
||||
end
|
||||
|
||||
def visit_ValidateConstraint(name)
|
||||
"VALIDATE CONSTRAINT #{quote_column_name(name)}"
|
||||
end
|
||||
|
|
|
@ -523,7 +523,7 @@ module ActiveRecord
|
|||
scope = quoted_scope(table_name)
|
||||
|
||||
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
|
||||
JOIN pg_class t ON c.conrelid = t.oid
|
||||
WHERE c.contype = 'c'
|
||||
|
@ -532,7 +532,8 @@ module ActiveRecord
|
|||
|
||||
check_info.map do |row|
|
||||
options = {
|
||||
name: row["conname"]
|
||||
name: row["conname"],
|
||||
validate: row["valid"]
|
||||
}
|
||||
expression = row["constraintdef"][/CHECK \({2}(.+)\){2}/, 1]
|
||||
|
||||
|
@ -632,6 +633,19 @@ module ActiveRecord
|
|||
validate_constraint from_table, fk_name_to_validate
|
||||
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
|
||||
def schema_creation
|
||||
PostgreSQL::SchemaCreation.new(self)
|
||||
|
|
|
@ -64,6 +64,43 @@ if ActiveRecord::Base.connection.supports_check_constraints?
|
|||
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
|
||||
@connection.add_check_constraint :trades, "price > 0", name: "price_check"
|
||||
@connection.add_check_constraint :trades, "quantity > 0", name: "quantity_check"
|
||||
|
|
Loading…
Reference in a new issue