Obtain required table lock during cross-table constraint updates. REL_13_STABLE github/REL_13_STABLE
authorTom Lane <[email protected]>
Sun, 29 Jun 2025 17:56:03 +0000 (13:56 -0400)
committerTom Lane <[email protected]>
Sun, 29 Jun 2025 17:56:03 +0000 (13:56 -0400)
Sometimes a table's constraint may depend on a column of another
table, so that we have to update the constraint when changing the
referenced column's type.  We need to have lock on the constraint's
table to do that.  ATPostAlterTypeCleanup believed that this case
was only possible for FOREIGN KEY constraints, but it's wrong at
least for CHECK and EXCLUDE constraints; and in general, we'd
probably need exclusive lock to alter any sort of constraint.
So just remove the contype check and acquire lock for any other
table.  This prevents a "you don't have lock" assertion failure,
though no ill effect is observed in production builds.

We'll error out later anyway because we don't presently support
physically altering column types within stored composite columns.
But the catalog-munging is basically all there, so we may as well
make that part work.

Bug: #18970
Reported-by: Alexander Lakhin <[email protected]>
Diagnosed-by: jian he <[email protected]>
Author: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/18970-a7d1cfe1f8d5d8d9@postgresql.org
Backpatch-through: 13

src/backend/commands/tablecmds.c
src/test/regress/expected/alter_table.out
src/test/regress/sql/alter_table.sql

index 53dc3296c8184128b84059abe6dd3fc17ff33ca2..3c680fade2b3e8882b3571f4d685967b3361bb79 100644 (file)
@@ -12560,9 +12560,12 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
    /*
     * Re-parse the index and constraint definitions, and attach them to the
     * appropriate work queue entries.  We do this before dropping because in
-    * the case of a FOREIGN KEY constraint, we might not yet have exclusive
-    * lock on the table the constraint is attached to, and we need to get
-    * that before reparsing/dropping.
+    * the case of a constraint on another table, we might not yet have
+    * exclusive lock on the table the constraint is attached to, and we need
+    * to get that before reparsing/dropping.  (That's possible at least for
+    * FOREIGN KEY, CHECK, and EXCLUSION constraints; in non-FK cases it
+    * requires a dependency on the target table's composite type in the other
+    * table's constraint expressions.)
     *
     * We can't rely on the output of deparsing to tell us which relation to
     * operate on, because concurrent activity might have made the name
@@ -12578,7 +12581,6 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
        Form_pg_constraint con;
        Oid         relid;
        Oid         confrelid;
-       char        contype;
        bool        conislocal;
 
        tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
@@ -12595,7 +12597,6 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
                elog(ERROR, "could not identify relation associated with constraint %u", oldId);
        }
        confrelid = con->confrelid;
-       contype = con->contype;
        conislocal = con->conislocal;
        ReleaseSysCache(tup);
 
@@ -12612,12 +12613,12 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
            continue;
 
        /*
-        * When rebuilding an FK constraint that references the table we're
-        * modifying, we might not yet have any lock on the FK's table, so get
-        * one now.  We'll need AccessExclusiveLock for the DROP CONSTRAINT
-        * step, so there's no value in asking for anything weaker.
+        * When rebuilding another table's constraint that references the
+        * table we're modifying, we might not yet have any lock on the other
+        * table, so get one now.  We'll need AccessExclusiveLock for the DROP
+        * CONSTRAINT step, so there's no value in asking for anything weaker.
         */
-       if (relid != tab->relid && contype == CONSTRAINT_FOREIGN)
+       if (relid != tab->relid)
            LockRelationOid(relid, AccessExclusiveLock);
 
        ATPostAlterTypeParse(oldId, relid, confrelid,
index 28afffad2f05b74b2c635b03b2f3cdbbe7008c3d..fe3f2f009dce3f258cab57b4fad319e0d0411dd1 100644 (file)
@@ -4508,6 +4508,13 @@ create trigger xtrig
 update bar1 set a = a + 1;
 INFO:  a=1, b=1
 /* End test case for bug #16242 */
+/* Test case for bug #18970 */
+create table attbl(a int);
+create table atref(b attbl check ((b).a is not null));
+alter table attbl alter column a type numeric;  -- someday this should work
+ERROR:  cannot alter table "attbl" because column "atref.b" uses its row type
+drop table attbl, atref;
+/* End test case for bug #18970 */
 -- Test that ALTER TABLE rewrite preserves a clustered index
 -- for normal indexes and indexes on constraints.
 create table alttype_cluster (a int);
index d1b9f067152b6ae0de834c4b7c68897ab792a635..06bcbb04c4618ece7ae8cd789d8a8093caf5cdf2 100644 (file)
@@ -2978,6 +2978,15 @@ update bar1 set a = a + 1;
 
 /* End test case for bug #16242 */
 
+/* Test case for bug #18970 */
+
+create table attbl(a int);
+create table atref(b attbl check ((b).a is not null));
+alter table attbl alter column a type numeric;  -- someday this should work
+drop table attbl, atref;
+
+/* End test case for bug #18970 */
+
 -- Test that ALTER TABLE rewrite preserves a clustered index
 -- for normal indexes and indexes on constraints.
 create table alttype_cluster (a int);