From 598ee4a46f83236b57455cfb074d213c4da88fb2 Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Fri, 23 Oct 2026 23:35:32 -0400 Subject: [PATCH v5 3/3] pg_dump: keep ENFORCED on a child's CHECK that no parent enforces An inheritance child can keep an inherited CHECK constraint ENFORCED while none of its remaining parents enforce it, for example after NO INHERIT or DROP CONSTRAINT on its other parent. pg_dump created it from the parents' NOT ENFORCED definition. Dump it as a local constraint instead, as a direct ALTER CONSTRAINT ... ENFORCED on the child would leave it. --- src/bin/pg_dump/pg_dump.c | 24 ++++++++++++++++++-- src/bin/pg_dump/t/002_pg_dump.pl | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index c8c854a49f4..9f3fa6d36fd 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -9771,6 +9771,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) int i_conislocal; int i_convalidated; int i_conenforced; + int i_childenforced; pg_log_info("finding table check constraints"); @@ -9780,9 +9781,15 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) "pg_catalog.pg_get_constraintdef(c.oid) AS consrc, " "conislocal, convalidated, "); if (fout->remoteVersion >= 180000) - appendPQExpBufferStr(q, "conenforced "); + appendPQExpBufferStr(q, + "conenforced, " + "(c.conenforced AND NOT c.conislocal AND NOT EXISTS " + "(SELECT 1 FROM pg_catalog.pg_inherits i " + "JOIN pg_catalog.pg_constraint p ON (p.conrelid = i.inhparent) " + "WHERE i.inhrelid = c.conrelid AND p.contype = 'c' " + "AND p.conname = c.conname AND p.conenforced)) AS childenforced "); else - appendPQExpBufferStr(q, "true AS conenforced "); + appendPQExpBufferStr(q, "true AS conenforced, false AS childenforced "); appendPQExpBuffer(q, "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" "JOIN pg_catalog.pg_constraint c ON (src.tbloid = c.conrelid)\n" @@ -9803,6 +9810,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) i_conislocal = PQfnumber(res, "conislocal"); i_convalidated = PQfnumber(res, "convalidated"); i_conenforced = PQfnumber(res, "conenforced"); + i_childenforced = PQfnumber(res, "childenforced"); /* As above, this loop iterates once per table, not once per row */ curtblindx = -1; @@ -9863,6 +9871,18 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) constrs[j].conislocal = (PQgetvalue(res, j, i_conislocal)[0] == 't'); constrs[j].conenforced = (PQgetvalue(res, j, i_conenforced)[0] == 't'); + /* + * An inherited constraint that is ENFORCED here while no + * parent enforces it would come back NOT ENFORCED from the + * parents' definitions, so dump it as if it were local, like + * a direct ALTER CONSTRAINT ... ENFORCED on the child makes + * it. Not for a NOT VALID one, since adding it to the child + * would merge it into the inherited copy and mark it valid. + */ + if (PQgetvalue(res, j, i_childenforced)[0] == 't' && validated && + !tbinfo->ispartition && !dopt->binary_upgrade) + constrs[j].conislocal = true; + /* * An unvalidated constraint needs to be dumped separately, so * that potentially-violating existing data is loaded before diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 7b09f0276d7..c9158f249c9 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -1138,6 +1138,45 @@ my %tests = ( }, }, + # The child is not local here, and sorts before its parents. + 'CONSTRAINT CHECK / ENFORCED only on child, after NO INHERIT' => { + create_sql => 'CREATE TABLE dump_test.test_table_ni_p1 (col1 int CONSTRAINT ck CHECK (col1 > 0)); + CREATE TABLE dump_test.test_table_ni_p2 (col1 int CONSTRAINT ck CHECK (col1 > 0) NOT ENFORCED); + CREATE TABLE dump_test.test_table_ni_c () INHERITS (dump_test.test_table_ni_p1, dump_test.test_table_ni_p2); + ALTER TABLE dump_test.test_table_ni_c NO INHERIT dump_test.test_table_ni_p1;', + regexp => qr/^ + \QCREATE TABLE dump_test.test_table_ni_c (\E\n + \s+\QCONSTRAINT ck CHECK ((col1 > 0))\E\n + /xm, + like => { + %full_runs, %dump_test_schema_runs, section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + only_dump_measurement => 1, + binary_upgrade => 1, + }, + }, + + 'CONSTRAINT CHECK / ENFORCED only on child, after parent DROP CONSTRAINT' => { + create_sql => 'CREATE TABLE dump_test.test_table_dc_p1 (col1 int CONSTRAINT ck CHECK (col1 > 0)); + CREATE TABLE dump_test.test_table_dc_p2 (col1 int CONSTRAINT ck CHECK (col1 > 0) NOT ENFORCED); + CREATE TABLE dump_test.test_table_dc_c () INHERITS (dump_test.test_table_dc_p1, dump_test.test_table_dc_p2); + ALTER TABLE dump_test.test_table_dc_p1 DROP CONSTRAINT ck;', + regexp => qr/^ + \QCREATE TABLE dump_test.test_table_dc_c (\E\n + \s+\QCONSTRAINT ck CHECK ((col1 > 0))\E\n + /xm, + like => { + %full_runs, %dump_test_schema_runs, section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + only_dump_measurement => 1, + binary_upgrade => 1, + }, + }, + 'CONSTRAINT CHECK / NOT VALID only on partition' => { create_sql => 'CREATE TABLE dump_test.test_table_nv (col1 int CONSTRAINT ck CHECK (col1 > 0) NOT ENFORCED) PARTITION BY LIST (col1); CREATE TABLE dump_test.test_table_nv_1 (col1 int); -- 2.37.1 (Apple Git-137.1)