1
0
Fork 0
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:
Alex Robbin 2020-09-06 22:04:34 -04:00
parent 580eefe1bc
commit 368da3ef29
No known key found for this signature in database
GPG key ID: 802FA5D5BE154BB7
6 changed files with 71 additions and 2 deletions

View file

@ -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.

View file

@ -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

View file

@ -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?

View file

@ -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

View file

@ -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)

View file

@ -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"