From b3662f7547ec7e8047c4b560a27bef0eb9cfd7a7 Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Mon, 5 Oct 2026 12:05:14 -0400 Subject: [PATCH v1 3/3] pg_dump: keep ENFORCED on a child's inherited CHECK constraint A child can enforce an inherited CHECK constraint that its parents keep NOT ENFORCED. pg_dump printed only the parent's definition, so the child came back NOT ENFORCED. Emit ALTER TABLE ... ALTER CONSTRAINT ... ENFORCED for such a child, after the parents' constraints. --- src/bin/pg_dump/pg_dump.c | 82 ++++++++++++++++++++++++++++++-- src/bin/pg_dump/pg_dump.h | 1 + src/bin/pg_dump/t/002_pg_dump.pl | 24 ++++++++++ 3 files changed, 102 insertions(+), 5 deletions(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 90971fbdac6..6ad3d7bcd92 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -9728,6 +9728,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) constrs[j].condeferrable = false; constrs[j].condeferred = false; constrs[j].conislocal = (PQgetvalue(res, j, i_conislocal)[0] == 't'); + constrs[j].childenforced = false; /* * All invalid not-null constraints must be dumped separately, @@ -9758,14 +9759,31 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) int i_consrc; int i_conislocal; int i_convalidated; + int i_childenforced; pg_log_info("finding table check constraints"); resetPQExpBuffer(q); + appendPQExpBufferStr(q, + "SELECT c.tableoid, c.oid, conrelid, conname, " + "pg_catalog.pg_get_constraintdef(c.oid) AS consrc, " + "conislocal, convalidated, "); + + /* + * An inherited constraint can be ENFORCED on a child while no parent + * enforces it. + */ + if (fout->remoteVersion >= 180000) + appendPQExpBufferStr(q, + "(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, "false AS childenforced "); + appendPQExpBuffer(q, - "SELECT c.tableoid, c.oid, conrelid, conname, " - "pg_catalog.pg_get_constraintdef(c.oid) AS consrc, " - "conislocal, convalidated " "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" "JOIN pg_catalog.pg_constraint c ON (src.tbloid = c.conrelid)\n" "WHERE contype = 'c' " @@ -9784,6 +9802,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) i_consrc = PQfnumber(res, "consrc"); i_conislocal = PQfnumber(res, "conislocal"); i_convalidated = PQfnumber(res, "convalidated"); + i_childenforced = PQfnumber(res, "childenforced"); /* As above, this loop iterates once per table, not once per row */ curtblindx = -1; @@ -9843,12 +9862,21 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) constrs[j].condeferred = false; constrs[j].conislocal = (PQgetvalue(res, j, i_conislocal)[0] == 't'); + /* + * Partitions and binary upgrade dump inherited constraints + * from the child's own definition, so this is not needed. + */ + constrs[j].childenforced = + (PQgetvalue(res, j, i_childenforced)[0] == 't' && + !tbinfo->ispartition && !dopt->binary_upgrade); + /* * An unvalidated constraint needs to be dumped separately, so * that potentially-violating existing data is loaded before - * the constraint. + * the constraint. One that is ENFORCED only on a child is + * altered after the parents' constraints are in place. */ - constrs[j].separate = !validated; + constrs[j].separate = !validated || constrs[j].childenforced; constrs[j].dobj.dump = tbinfo->dobj.dump; @@ -9872,6 +9900,31 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) } } + /* + * A separately dumped inherited constraint must come after the same + * constraint of each parent. + */ + for (int j = 0; j < numConstrs; j++) + { + TableInfo *tbinfo = constrs[j].contable; + + if (!constrs[j].separate || constrs[j].conislocal) + continue; + + for (int p = 0; p < tbinfo->numParents; p++) + { + TableInfo *parent = tbinfo->parents[p]; + + for (int c = 0; parent->checkexprs && c < parent->ncheck; c++) + { + if (strcmp(parent->checkexprs[c].dobj.name, + constrs[j].dobj.name) == 0) + addObjectDependency(&constrs[j].dobj, + parent->checkexprs[c].dobj.dumpId); + } + } + } + PQclear(res); } @@ -18978,6 +19031,25 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) .createStmt = q->data, .dropStmt = delq->data)); } + else if (coninfo->childenforced) + { + /* the parents' definitions created it as NOT ENFORCED */ + appendPQExpBuffer(q, "ALTER %sTABLE %s ", foreign, + fmtQualifiedDumpable(tbinfo)); + appendPQExpBuffer(q, "ALTER CONSTRAINT %s ENFORCED;\n", + fmtId(coninfo->dobj.name)); + + tag = psprintf("%s %s", tbinfo->dobj.name, coninfo->dobj.name); + + if (coninfo->dobj.dump & DUMP_COMPONENT_DEFINITION) + ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId, + ARCHIVE_OPTS(.tag = tag, + .namespace = tbinfo->dobj.namespace->dobj.name, + .owner = tbinfo->rolname, + .description = "CHECK CONSTRAINT", + .section = SECTION_POST_DATA, + .createStmt = q->data)); + } } else if (tbinfo == NULL) { diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 2bbb5d5773b..e1c724fc7ea 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -524,6 +524,7 @@ typedef struct _constraintInfo bool condeferred; /* true if constraint is INITIALLY DEFERRED */ bool conperiod; /* true if the constraint is WITHOUT OVERLAPS */ bool conislocal; /* true if constraint has local definition */ + bool childenforced; /* true if inherited, but ENFORCED only here */ bool separate; /* true if must dump as separate item */ } ConstraintInfo; diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 63d80268ca2..d431c4890b9 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -1120,6 +1120,30 @@ my %tests = ( }, }, + # The child sorts before the parent, but must be altered after it. + 'CONSTRAINT CHECK / ENFORCED only on child' => { + create_sql => 'CREATE TABLE dump_test.test_table_ne_parent ( + col1 int, + CONSTRAINT ck CHECK (col1 > 0) NOT ENFORCED); + CREATE TABLE dump_test.test_table_ne_child ( + ) INHERITS (dump_test.test_table_ne_parent); + ALTER TABLE dump_test.test_table_ne_child ALTER CONSTRAINT ck ENFORCED;', + regexp => qr/^ + \QALTER TABLE dump_test.test_table_ne_parent\E \n^\s+ + \QADD CONSTRAINT ck CHECK ((col1 > 0)) NOT ENFORCED;\E + .*^ + \QALTER TABLE dump_test.test_table_ne_child ALTER CONSTRAINT ck ENFORCED;\E + /xms, + like => { + %full_runs, %dump_test_schema_runs, section_post_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + only_dump_measurement => 1, + binary_upgrade => 1, + }, + }, + 'CONSTRAINT NOT NULL / NO INHERIT' => { create_sql => 'CREATE TABLE dump_test.test_table_nonn ( col1 int NOT NULL NO INHERIT, -- 2.37.1 (Apple Git-137.1)