Re: Fix bug of CHECK constraint enforceability recursion

From: jian he <jian(dot)universality(at)gmail(dot)com>
To: Chao Li <li(dot)evan(dot)chao(at)gmail(dot)com>
Cc: Álvaro Herrera <alvherre(at)kurilemu(dot)de>, "L(dot) pgsql-hackers" <pgsql-hackers(at)lists(dot)postgresql(dot)org>, Andrew Dunstan <andrew(at)dunslane(dot)net>
Subject: Re: Fix bug of CHECK constraint enforceability recursion
Date: 2026-05-28 02:31:35
Message-ID: CACJufxFwJH5K1jgL1ryckYX8gxp1sfqz_2ypey1sYtWZ5u45oQ@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Wed, May 27, 2026 at 2:20 PM Chao Li <li(dot)evan(dot)chao(at)gmail(dot)com> wrote:
>
> 4. It cannot handle some complicated inheritance hierarchies. For example, the following test passes with your v1:
> ```
> evantest=# CREATE TABLE p1 (a int CONSTRAINT c CHECK (a > 0) ENFORCED);
> CREATE TABLE
> evantest=# CREATE TABLE p2 (a int CONSTRAINT c CHECK (a > 0) ENFORCED);
> CREATE TABLE
> evantest=#
> evantest=# CREATE TABLE ch () INHERITS (p1, p2);
> NOTICE: merging multiple inherited definitions of column "a"
> CREATE TABLE
> evantest=# ALTER TABLE p1 ALTER CONSTRAINT c NOT ENFORCED;
> ALTER TABLE
> ```
>
> I originally thought this should fail, but it now changes ch.c to NOT ENFORCED, so it breaks the rule because its parent p2 is still ENFORCED:
> ```
> evantest=# SELECT conrelid::regclass, conname, conenforced, coninhcount, conislocal
> evantest-# FROM pg_constraint WHERE conname = 'c';
> conrelid | conname | conenforced | coninhcount | conislocal
> ----------+---------+-------------+-------------+------------
> p1 | c | f | 0 | t
> p2 | c | t | 0 | t
> ch | c | f | 2 | f
> (3 rows)
> ```
>
> Then I realized that the initial CREATE TABLE case passes:
> ```
> evantest=# CREATE TABLE p1 (a int CONSTRAINT c CHECK (a > 0) NOT ENFORCED);
> CREATE TABLE
> evantest=# CREATE TABLE p2 (a int CONSTRAINT c CHECK (a > 0) ENFORCED);
> CREATE TABLE
> evantest=# CREATE TABLE ch () INHERITS (p1, p2);
> NOTICE: merging multiple inherited definitions of column "a"
> CREATE TABLE
> evantest=# SELECT conrelid::regclass, conname, conenforced, coninhcount, conislocal
> evantest-# FROM pg_constraint WHERE conname = ‘c';
> conrelid | conname | conenforced | coninhcount | conislocal
> ----------+---------+-------------+-------------+------------
> ch | c | t | 2 | f
> p1 | c | f | 0 | t
> p2 | c | t | 0 | t
> (3 rows)
> ```
>
> When the two parents have different enforceability, the stricter one is applied to the child. So I think the test above in item 4 should also perform similar merge logic rather than fail. This seems to uncover a new issue in the original feature patch.
>

> For the fix, my design is:
>
> * Directly reject changing an inherited child CHECK constraint to NOT ENFORCED if an equivalent parent constraint remains ENFORCED.
> * Changing a child to ENFORCED is allowed.
> * During recursing, if a child also inherits an equivalent ENFORCED constraint from another parent outside the current ALTER, the child keeps the stricter ENFORCED state.
>
> Please see my implementation in the attached v2 patch.

CREATE TABLE p1 (a int CONSTRAINT c CHECK (a > 0) ENFORCED);
CREATE TABLE p2 (a int CONSTRAINT c CHECK (a > 0) ENFORCED);
CREATE TABLE ch () INHERITS (p1, p2);
ALTER TABLE p1 ALTER CONSTRAINT c NOT ENFORCED;

The v2 patch marks check constraint c on table ch as ENFORCED, which
seems to contradict the documentation's wording:
https://www.postgresql.org/docs/devel/ddl-inherit.html
<<>>
ALTER TABLE will propagate any changes in column data definitions and check
constraints down the inheritance hierarchy. Again, dropping columns that are
depended on by other tables is only possible when using the CASCADE option.
ALTER TABLE follows the same rules for duplicate column merging and rejection
that apply during CREATE TABLE
<<>>

The wording (https://www.postgresql.org/docs/devel/ddl-inherit.html)
below also discourages directly altering check constraints on child tables.
<<>>
A parent table cannot be dropped while any of its children remain. Neither can
columns or check constraints of child tables be dropped or altered if they are
inherited from any parent tables. If you wish to remove a table and all of its
descendants, one easy way is to drop the parent table with the CASCADE option
(see Section 5.17).
<<>>

--
jian
https://www.enterprisedb.com/

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Michael Paquier 2026-05-28 02:43:29 injection_points: Switch wait/wakeup to use atomics rather than latches
Previous Message Tomas Vondra 2026-05-28 01:29:15 Re: Is there value in having optimizer stats for joins/foreignkeys?