]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix some more bugs in foreign keys connecting partitioned tables
authorÁlvaro Herrera <alvherre@alvh.no-ip.org>
Wed, 30 Oct 2024 09:54:03 +0000 (10:54 +0100)
committerÁlvaro Herrera <alvherre@alvh.no-ip.org>
Wed, 30 Oct 2024 09:54:03 +0000 (10:54 +0100)
* In DetachPartitionFinalize() we were applying a tuple conversion map
  to tuples that didn't need one, which can lead to erratic behavior if
  a partitioned table has a partition with a different column order, as
  reported by Alexander Lakhin. This was introduced by 53af9491a043.
  Don't do that.  Also, modify a recently added test case to exercise
  this.

* The same function as well as CloneFkReferenced() were acquiring
  AccessShareLock on a partition, only to have CreateTrigger() later
  acquire ShareRowExclusiveLock on it.  This can lead to deadlock by
  lock escalation, unnecessarily.  Avoid that by acquiring the stronger
  lock to begin with.  This probably dates back to branch 12, but I have
  never seen a report of this being a problem in the field.

* Innocuous but wasteful: also introduced by 53af9491a043, we were
  reading a pg_constraint tuple from syscache that we don't need, as
  reported by Tender Wang.  Don't.

Backpatch to 15.

Discussion: https://postgr.es/m/461e9c26-2076-8224-e119-84998b6a784e@gmail.com

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

index b6968d23ed5d95c7dfbea4e6402ee0662f3a3d7b..1402755c36c3c02462954f3fcfa2b42ef6169305 100644 (file)
@@ -9844,6 +9844,9 @@ addFkRecurseReferenced(Constraint *fkconstraint, Relation rel,
        Oid                     deleteTriggerOid,
                                updateTriggerOid;
 
+       Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
+       Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
+
        /*
         * Create the action triggers that enforce the constraint.
         */
@@ -9870,6 +9873,7 @@ addFkRecurseReferenced(Constraint *fkconstraint, Relation rel,
                        Oid                     partIndexId;
                        ObjectAddress address;
 
+                       /* XXX would it be better to acquire these locks beforehand? */
                        partRel = table_open(pd->oids[i], ShareRowExclusiveLock);
 
                        /*
@@ -9970,7 +9974,9 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
        Oid                     insertTriggerOid,
                                updateTriggerOid;
 
-       AssertArg(OidIsValid(parentConstr));
+       Assert(OidIsValid(parentConstr));
+       Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
+       Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
 
        if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
                ereport(ERROR,
@@ -10259,13 +10265,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
                        continue;
                }
 
-               /*
-                * Because we're only expanding the key space at the referenced side,
-                * we don't need to prevent any operation in the referencing table, so
-                * AccessShareLock suffices (assumes that dropping the constraint
-                * acquires AccessExclusiveLock).
-                */
-               fkRel = table_open(constrForm->conrelid, AccessShareLock);
+               /* We need the same lock level that CreateTrigger will acquire */
+               fkRel = table_open(constrForm->conrelid, ShareRowExclusiveLock);
 
                indexOid = constrForm->conindid;
                DeconstructFkConstraintRow(tuple,
@@ -18822,8 +18823,7 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
        foreach(cell, fks)
        {
                ForeignKeyCacheInfo *fk = lfirst(cell);
-               HeapTuple       contup,
-                                       parentConTup;
+               HeapTuple       contup;
                Form_pg_constraint conform;
                Oid                     insertTriggerOid,
                                        updateTriggerOid;
@@ -18841,13 +18841,6 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
                        continue;
                }
 
-               Assert(OidIsValid(conform->conparentid));
-               parentConTup = SearchSysCache1(CONSTROID,
-                                                                          ObjectIdGetDatum(conform->conparentid));
-               if (!HeapTupleIsValid(parentConTup))
-                       elog(ERROR, "cache lookup failed for constraint %u",
-                                conform->conparentid);
-
                /*
                 * The constraint on this table must be marked no longer a child of
                 * the parent's constraint, as do its check triggers.
@@ -18888,7 +18881,6 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
                        Oid                     conffeqop[INDEX_MAX_KEYS];
                        int                     numfkdelsetcols;
                        AttrNumber      confdelsetcols[INDEX_MAX_KEYS];
-                       AttrMap    *attmap;
                        Relation        refdRel;
 
                        DeconstructFkConstraintRow(contup,
@@ -18921,19 +18913,19 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
                        fkconstraint->old_pktable_oid = InvalidOid;
                        fkconstraint->location = -1;
 
-                       attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-                                                                                  RelationGetDescr(rel));
+                       /* set up colnames, used to generate the constraint name */
                        for (int i = 0; i < numfks; i++)
                        {
                                Form_pg_attribute att;
 
                                att = TupleDescAttr(RelationGetDescr(partRel),
-                                                                       attmap->attnums[conkey[i] - 1] - 1);
+                                                                       conkey[i] - 1);
+
                                fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
                                                                                                 makeString(NameStr(att->attname)));
                        }
 
-                       refdRel = table_open(fk->confrelid, AccessShareLock);
+                       refdRel = table_open(fk->confrelid, ShareRowExclusiveLock);
 
                        addFkRecurseReferenced(fkconstraint, partRel,
                                                                   refdRel,
@@ -18949,11 +18941,10 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
                                                                   confdelsetcols,
                                                                   true,
                                                                   InvalidOid, InvalidOid);
-                       table_close(refdRel, AccessShareLock);
+                       table_close(refdRel, NoLock);   /* keep lock till end of xact */
                }
 
                ReleaseSysCache(contup);
-               ReleaseSysCache(parentConTup);
        }
        list_free_deep(fks);
        if (trigrel)
index 02cf78ee26a3661fd7c4ac426cfa71bec8db1faa..fb9e7603effbfcf9cb13633219fba0132823007d 100644 (file)
@@ -2944,17 +2944,20 @@ CREATE SCHEMA fkpart12
   CREATE TABLE fk_p ( id int, jd int, PRIMARY KEY(id, jd)) PARTITION BY list (id)
   CREATE TABLE fk_p_1 PARTITION OF fk_p FOR VALUES IN (1) PARTITION BY list (jd)
   CREATE TABLE fk_p_1_1 PARTITION OF fk_p_1 FOR VALUES IN (1)
-  CREATE TABLE fk_p_1_2 PARTITION OF fk_p_1 FOR VALUES IN (2)
+  CREATE TABLE fk_p_1_2 (x int, y int, jd int NOT NULL, id int NOT NULL)
   CREATE TABLE fk_p_2 PARTITION OF fk_p FOR VALUES IN (2) PARTITION BY list (jd)
   CREATE TABLE fk_p_2_1 PARTITION OF fk_p_2 FOR VALUES IN (1)
   CREATE TABLE fk_p_2_2 PARTITION OF fk_p_2 FOR VALUES IN (2)
-  CREATE TABLE fk_r_1 ( id int PRIMARY KEY, p_id int NOT NULL, p_jd int NOT NULL)
+  CREATE TABLE fk_r_1 ( p_jd int NOT NULL, x int, id int PRIMARY KEY, p_id int NOT NULL)
   CREATE TABLE fk_r_2 ( id int PRIMARY KEY, p_id int NOT NULL, p_jd int NOT NULL) PARTITION BY list (id)
   CREATE TABLE fk_r_2_1 PARTITION OF fk_r_2 FOR VALUES IN (2, 1)
   CREATE TABLE fk_r   ( id int PRIMARY KEY, p_id int NOT NULL, p_jd int NOT NULL,
        FOREIGN KEY (p_id, p_jd) REFERENCES fk_p (id, jd)
   ) PARTITION BY list (id);
 SET search_path TO fkpart12;
+ALTER TABLE fk_p_1_2 DROP COLUMN x, DROP COLUMN y;
+ALTER TABLE fk_p_1 ATTACH PARTITION fk_p_1_2 FOR VALUES IN (2);
+ALTER TABLE fk_r_1 DROP COLUMN x;
 INSERT INTO fk_p VALUES (1, 1);
 ALTER TABLE fk_r ATTACH PARTITION fk_r_1 FOR VALUES IN (1);
 ALTER TABLE fk_r ATTACH PARTITION fk_r_2 FOR VALUES IN (2);
@@ -2993,7 +2996,7 @@ Foreign-key constraints:
     "fk_r_p_id_p_jd_fkey" FOREIGN KEY (p_id, p_jd) REFERENCES fk_p(id, jd)
 Number of partitions: 1 (Use \d+ to list them.)
 
-INSERT INTO fk_r_1 VALUES (2, 1, 2); -- should fail
+INSERT INTO fk_r_1 (id, p_id, p_jd) VALUES (2, 1, 2); -- should fail
 ERROR:  insert or update on table "fk_r_1" violates foreign key constraint "fk_r_p_id_p_jd_fkey"
 DETAIL:  Key (p_id, p_jd)=(1, 2) is not present in table "fk_p".
 DELETE FROM fk_p; -- should fail
index 9f424f2e143decc33831b9d28a471c3c2a48b2e3..4128e4e404962038ffae33cbab67ba70ce8b4ce9 100644 (file)
@@ -2097,11 +2097,11 @@ CREATE SCHEMA fkpart12
   CREATE TABLE fk_p ( id int, jd int, PRIMARY KEY(id, jd)) PARTITION BY list (id)
   CREATE TABLE fk_p_1 PARTITION OF fk_p FOR VALUES IN (1) PARTITION BY list (jd)
   CREATE TABLE fk_p_1_1 PARTITION OF fk_p_1 FOR VALUES IN (1)
-  CREATE TABLE fk_p_1_2 PARTITION OF fk_p_1 FOR VALUES IN (2)
+  CREATE TABLE fk_p_1_2 (x int, y int, jd int NOT NULL, id int NOT NULL)
   CREATE TABLE fk_p_2 PARTITION OF fk_p FOR VALUES IN (2) PARTITION BY list (jd)
   CREATE TABLE fk_p_2_1 PARTITION OF fk_p_2 FOR VALUES IN (1)
   CREATE TABLE fk_p_2_2 PARTITION OF fk_p_2 FOR VALUES IN (2)
-  CREATE TABLE fk_r_1 ( id int PRIMARY KEY, p_id int NOT NULL, p_jd int NOT NULL)
+  CREATE TABLE fk_r_1 ( p_jd int NOT NULL, x int, id int PRIMARY KEY, p_id int NOT NULL)
   CREATE TABLE fk_r_2 ( id int PRIMARY KEY, p_id int NOT NULL, p_jd int NOT NULL) PARTITION BY list (id)
   CREATE TABLE fk_r_2_1 PARTITION OF fk_r_2 FOR VALUES IN (2, 1)
   CREATE TABLE fk_r   ( id int PRIMARY KEY, p_id int NOT NULL, p_jd int NOT NULL,
@@ -2109,6 +2109,10 @@ CREATE SCHEMA fkpart12
   ) PARTITION BY list (id);
 SET search_path TO fkpart12;
 
+ALTER TABLE fk_p_1_2 DROP COLUMN x, DROP COLUMN y;
+ALTER TABLE fk_p_1 ATTACH PARTITION fk_p_1_2 FOR VALUES IN (2);
+ALTER TABLE fk_r_1 DROP COLUMN x;
+
 INSERT INTO fk_p VALUES (1, 1);
 
 ALTER TABLE fk_r ATTACH PARTITION fk_r_1 FOR VALUES IN (1);
@@ -2124,7 +2128,7 @@ ALTER TABLE fk_r DETACH PARTITION fk_r_2;
 
 \d fk_r_2
 
-INSERT INTO fk_r_1 VALUES (2, 1, 2); -- should fail
+INSERT INTO fk_r_1 (id, p_id, p_jd) VALUES (2, 1, 2); -- should fail
 DELETE FROM fk_p; -- should fail
 
 ALTER TABLE fk_r ATTACH PARTITION fk_r_1 FOR VALUES IN (1);