From 989b965242a9d3e9003aa1b1b9fb2c9708cb3054 Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Fri, 9 Oct 2026 00:04:00 -0400 Subject: [PATCH v1 1/3] Validate NOT VALID children when a parent CHECK becomes ENFORCED ALTER CONSTRAINT ... ENFORCED on a parent skipped a child whose constraint was already ENFORCED but NOT VALID. The parent was then marked validated while the child could still hold bad rows, and constraint exclusion could drop them from query results. Validate such children too. --- src/backend/commands/tablecmds.c | 15 ++++++++++++--- src/test/regress/expected/inherit.out | 22 ++++++++++++++++++++++ src/test/regress/sql/inherit.sql | 15 +++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 93ad49e59fd..fdfe4231ead 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -12676,6 +12676,7 @@ ATExecAlterCheckConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon, Form_pg_constraint currcon; Relation rel; bool changed = false; + bool validate; List *children = NIL; bool target_enforced = cmdcon->is_enforced; Oid enforced_parentoid = InvalidOid; @@ -12727,12 +12728,20 @@ ATExecAlterCheckConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon, target_enforced = true; } + /* + * A constraint that becomes enforced is also marked validated, so a + * descendant that is already enforced but not yet valid must be validated + * too. + */ + validate = recursing && cmdcon->is_enforced && + currcon->conenforced && !currcon->convalidated; + /* * Update to the merged enforceability if needed. This may differ from the * requested enforceability when another matching parent constraint * remains enforced. */ - if (currcon->conenforced != target_enforced) + if (currcon->conenforced != target_enforced || validate) { ATAlterConstraint updatecon = *cmdcon; @@ -12825,10 +12834,10 @@ ATExecAlterCheckConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon, /* * Tell Phase 3 to check that the constraint is satisfied by existing * rows. We only need do this when altering the constraint from NOT - * ENFORCED to ENFORCED. + * ENFORCED to ENFORCED, or when validating a descendant as above. */ if (rel->rd_rel->relkind == RELKIND_RELATION && - !currcon->conenforced && + (!currcon->conenforced || validate) && target_enforced) { AlteredTableInfo *tab; diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index 0136aa53c96..7b30876866c 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -1479,6 +1479,28 @@ NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to table p1_c1 drop cascades to table p1_c2 drop cascades to table p1_c3 +-- ALTER CONSTRAINT ... ENFORCED on a parent must also validate a child whose +-- constraint is already enforced but NOT VALID +create table p1(f1 int constraint inh_check_constraint check (f1 > 0) not enforced); +create table p1_c1(f1 int); +insert into p1_c1 values(-1); +alter table p1_c1 add constraint inh_check_constraint check (f1 > 0) not valid; +alter table p1_c1 inherit p1; +alter table p1 alter constraint inh_check_constraint enforced; --error +ERROR: check constraint "inh_check_constraint" of relation "p1_c1" is violated by some row +delete from p1_c1; +alter table p1 alter constraint inh_check_constraint enforced; --ok +select conrelid::regclass, conenforced, convalidated +from pg_constraint where conname = 'inh_check_constraint' +order by conrelid::regclass::text collate "C"; + conrelid | conenforced | convalidated +----------+-------------+-------------- + p1 | t | t + p1_c1 | t | t +(2 rows) + +drop table p1 cascade; +NOTICE: drop cascades to table p1_c1 -- an inherited CHECK constraint cannot be NOT ENFORCED under an ENFORCED parent create table p1(f1 int constraint p1_a_check check (f1 > 0) enforced); create table p1_c1() inherits(p1); diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql index 072fca13c13..626d2cde378 100644 --- a/src/test/regress/sql/inherit.sql +++ b/src/test/regress/sql/inherit.sql @@ -535,6 +535,21 @@ where conname = 'inh_check_constraint3' and contype = 'c' order by conrelid::regclass::text collate "C"; drop table p1 cascade; +-- ALTER CONSTRAINT ... ENFORCED on a parent must also validate a child whose +-- constraint is already enforced but NOT VALID +create table p1(f1 int constraint inh_check_constraint check (f1 > 0) not enforced); +create table p1_c1(f1 int); +insert into p1_c1 values(-1); +alter table p1_c1 add constraint inh_check_constraint check (f1 > 0) not valid; +alter table p1_c1 inherit p1; +alter table p1 alter constraint inh_check_constraint enforced; --error +delete from p1_c1; +alter table p1 alter constraint inh_check_constraint enforced; --ok +select conrelid::regclass, conenforced, convalidated +from pg_constraint where conname = 'inh_check_constraint' +order by conrelid::regclass::text collate "C"; +drop table p1 cascade; + -- an inherited CHECK constraint cannot be NOT ENFORCED under an ENFORCED parent create table p1(f1 int constraint p1_a_check check (f1 > 0) enforced); create table p1_c1() inherits(p1); -- 2.37.1 (Apple Git-137.1)