1
0
mirror of https://github.com/postgres/postgres.git synced 2025-12-02 23:42:46 +03:00

Fix restore of not-null constraints with inheritance

In tables with primary keys, pg_dump creates tables with primary keys by
initially dumping them with throw-away not-null constraints (marked "no
inherit" so that they don't create problems elsewhere), to later drop
them once the primary key is restored.  Because of a unrelated
consideration, on tables with children we add not-null constraints to
all columns of the primary key when it is created.

If both a table and its child have primary keys, and pg_dump happens to
emit the child table first (and its throw-away not-null) and later its
parent table, the creation of the parent's PK will fail because the
throw-away not-null constraint collides with the permanent not-null
constraint that the PK wants to add, so the dump fails to restore.

We can work around this problem by letting the primary key "take over"
the child's not-null.  This requires no changes to pg_dump, just two
changes to ALTER TABLE: first, the ability to convert a no-inherit
not-null constraint into a regular inheritable one (including recursing
down to children, if there are any); second, the ability to "drop" a
constraint that is defined both directly in the table and inherited from
a parent (which simply means to mark it as no longer having a local
definition).

Secondarily, change ATPrepAddPrimaryKey() to acquire locks all the way
down the inheritance hierarchy, in case we need to recurse when
propagating constraints.

These two changes allow pg_dump to reproduce more cases involving
inheritance from versions 16 and older.

Lastly, make two changes to pg_dump: 1) do not try to drop a not-null
constraint that's marked as inherited; this allows a dump to restore
with no errors if a table with a PK inherits from another which also has
a PK; 2) avoid giving inherited constraints throwaway names, for the
rare cases where such a constraint survives after the restore.

Reported-by: Andrew Bille <andrewbille@gmail.com>
Reported-by: Justin Pryzby <pryzby@telsasoft.com>
Discussion: https://postgr.es/m/CAJnzarwkfRu76_yi3dqVF_WL-MpvT54zMwAxFwJceXdHB76bOA@mail.gmail.com
Discussion: https://postgr.es/m/Zh0aAH7tbZb-9HbC@pryzbyj2023
This commit is contained in:
Alvaro Herrera
2024-04-18 15:35:15 +02:00
parent e0d51e3bf4
commit d9f686a72e
7 changed files with 221 additions and 29 deletions

View File

@@ -9336,7 +9336,6 @@ ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
{
List *children;
List *newconstrs = NIL;
ListCell *lc;
IndexStmt *indexstmt;
/* No work if not creating a primary key */
@@ -9351,11 +9350,19 @@ ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
!rel->rd_rel->relhassubclass)
return;
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
/*
* Acquire locks all the way down the hierarchy. The recursion to lower
* levels occurs at execution time as necessary, so we don't need to do it
* here, and we don't need the returned list either.
*/
(void) find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
foreach(lc, indexstmt->indexParams)
/*
* Construct the list of constraints that we need to add to each child
* relation.
*/
foreach_node(IndexElem, elem, indexstmt->indexParams)
{
IndexElem *elem = lfirst_node(IndexElem, lc);
Constraint *nnconstr;
Assert(elem->expr == NULL);
@@ -9374,9 +9381,10 @@ ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
newconstrs = lappend(newconstrs, nnconstr);
}
foreach(lc, children)
/* Finally, add AT subcommands to add each constraint to each child. */
children = find_inheritance_children(RelationGetRelid(rel), NoLock);
foreach_oid(childrelid, children)
{
Oid childrelid = lfirst_oid(lc);
Relation childrel = table_open(childrelid, NoLock);
AlterTableCmd *newcmd = makeNode(AlterTableCmd);
ListCell *lc2;
@@ -12942,6 +12950,31 @@ dropconstraint_internal(Relation rel, HeapTuple constraintTup, DropBehavior beha
con = (Form_pg_constraint) GETSTRUCT(constraintTup);
constrName = NameStr(con->conname);
/*
* If we're asked to drop a constraint which is both defined locally and
* inherited, we can simply mark it as no longer having a local
* definition, and no further changes are required.
*
* XXX We do this for not-null constraints only, not CHECK, because the
* latter have historically not behaved this way and it might be confusing
* to change the behavior now.
*/
if (con->contype == CONSTRAINT_NOTNULL &&
con->conislocal && con->coninhcount > 0)
{
HeapTuple copytup;
copytup = heap_copytuple(constraintTup);
con = (Form_pg_constraint) GETSTRUCT(copytup);
con->conislocal = false;
CatalogTupleUpdate(conrel, &copytup->t_self, copytup);
ObjectAddressSet(conobj, ConstraintRelationId, con->oid);
CommandCounterIncrement();
table_close(conrel, RowExclusiveLock);
return conobj;
}
/* Don't allow drop of inherited constraints */
if (con->coninhcount > 0 && !recursing)
ereport(ERROR,
@@ -16620,7 +16653,25 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
errmsg("too many inheritance parents"));
if (child_con->contype == CONSTRAINT_NOTNULL &&
child_con->connoinherit)
{
/*
* If the child has children, it's not possible to turn a NO
* INHERIT constraint into an inheritable one: we would need
* to recurse to create constraints in those children, but
* this is not a good place to do that.
*/
if (child_rel->rd_rel->relhassubclass)
ereport(ERROR,
errmsg("cannot add NOT NULL constraint to column \"%s\" of relation \"%s\" with inheritance children",
get_attname(RelationGetRelid(child_rel),
extractNotNullColumn(child_tuple),
false),
RelationGetRelationName(child_rel)),
errdetail("Existing constraint \"%s\" is marked NO INHERIT.",
NameStr(child_con->conname)));
child_con->connoinherit = false;
}
/*
* In case of partitions, an inherited constraint must be
@@ -20225,7 +20276,7 @@ ATExecDetachPartitionFinalize(Relation rel, RangeVar *name)
* DetachAddConstraintIfNeeded
* Subroutine for ATExecDetachPartition. Create a constraint that
* takes the place of the partition constraint, but avoid creating
* a dupe if an constraint already exists which implies the needed
* a dupe if a constraint already exists which implies the needed
* constraint.
*/
static void