refactor: Split tryAttachPartitionForeignKey()
authorPeter Eisentraut <[email protected]>
Tue, 11 Mar 2025 08:33:36 +0000 (09:33 +0100)
committerPeter Eisentraut <[email protected]>
Tue, 11 Mar 2025 08:35:24 +0000 (09:35 +0100)
Split tryAttachPartitionForeignKey() into three functions:
AttachPartitionForeignKey(), RemoveInheritedConstraint(), and
DropForeignKeyConstraintTriggers(), so they can be reused in some
subsequent patches for the NOT ENFORCED feature.

Author: Amul Sul <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Discussion: https://www.postgresql.org/message-id/flat/CAAJ_b962c5AcYW9KUt_R_ER5qs3fUGbe4az-SP-vuwPS-w-AGA%40mail.gmail.com

src/backend/commands/tablecmds.c

index e9b95018199a5b24742ca58609fd7361875ef0a5..1ada38908c19746278c9661b12a09515d71d0c71 100644 (file)
@@ -585,6 +585,14 @@ static bool tryAttachPartitionForeignKey(List **wqueue,
                                         Oid parentInsTrigger,
                                         Oid parentUpdTrigger,
                                         Relation trigrel);
+static void AttachPartitionForeignKey(List **wqueue, Relation partition,
+                                     Oid partConstrOid, Oid parentConstrOid,
+                                     Oid parentInsTrigger, Oid parentUpdTrigger,
+                                     Relation trigrel);
+static void RemoveInheritedConstraint(Relation conrel, Relation trigrel,
+                                     Oid conoid, Oid conrelid);
+static void DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid,
+                                            Oid confrelid, Oid conrelid);
 static void GetForeignKeyActionTriggers(Relation trigrel,
                                        Oid conoid, Oid confrelid, Oid conrelid,
                                        Oid *deleteTriggerOid,
@@ -11467,12 +11475,6 @@ tryAttachPartitionForeignKey(List **wqueue,
    Form_pg_constraint parentConstr;
    HeapTuple   partcontup;
    Form_pg_constraint partConstr;
-   bool        queueValidation;
-   ScanKeyData key;
-   SysScanDesc scan;
-   HeapTuple   trigtup;
-   Oid         insertTriggerOid,
-               updateTriggerOid;
 
    parentConstrTup = SearchSysCache1(CONSTROID,
                                      ObjectIdGetDatum(parentConstrOid));
@@ -11517,6 +11519,59 @@ tryAttachPartitionForeignKey(List **wqueue,
        return false;
    }
 
+   ReleaseSysCache(parentConstrTup);
+   ReleaseSysCache(partcontup);
+
+   /* Looks good!  Attach this constraint. */
+   AttachPartitionForeignKey(wqueue, partition, fk->conoid,
+                             parentConstrOid, parentInsTrigger,
+                             parentUpdTrigger, trigrel);
+
+   return true;
+}
+
+/*
+ * AttachPartitionForeignKey
+ *
+ * The subroutine for tryAttachPartitionForeignKey performs the final tasks of
+ * attaching the constraint, removing redundant triggers and entries from
+ * pg_constraint, and setting the constraint's parent.
+ */
+static void
+AttachPartitionForeignKey(List **wqueue,
+                         Relation partition,
+                         Oid partConstrOid,
+                         Oid parentConstrOid,
+                         Oid parentInsTrigger,
+                         Oid parentUpdTrigger,
+                         Relation trigrel)
+{
+   HeapTuple   parentConstrTup;
+   Form_pg_constraint parentConstr;
+   HeapTuple   partcontup;
+   Form_pg_constraint partConstr;
+   bool        queueValidation;
+   Oid         partConstrFrelid;
+   Oid         partConstrRelid;
+   Oid         insertTriggerOid,
+               updateTriggerOid;
+
+   /* Fetch the parent constraint tuple */
+   parentConstrTup = SearchSysCache1(CONSTROID,
+                                     ObjectIdGetDatum(parentConstrOid));
+   if (!HeapTupleIsValid(parentConstrTup))
+       elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
+   parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
+
+   /* Fetch the child constraint tuple */
+   partcontup = SearchSysCache1(CONSTROID,
+                                ObjectIdGetDatum(partConstrOid));
+   if (!HeapTupleIsValid(partcontup))
+       elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
+   partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
+   partConstrFrelid = partConstr->confrelid;
+   partConstrRelid = partConstr->conrelid;
+
    /*
     * Will we need to validate this constraint?   A valid parent constraint
     * implies that all child constraints have been validated, so if this one
@@ -11528,50 +11583,15 @@ tryAttachPartitionForeignKey(List **wqueue,
    ReleaseSysCache(parentConstrTup);
 
    /*
-    * Looks good!  Attach this constraint.  The action triggers in the new
-    * partition become redundant -- the parent table already has equivalent
-    * ones, and those will be able to reach the partition.  Remove the ones
-    * in the partition.  We identify them because they have our constraint
-    * OID, as well as being on the referenced rel.
+    * The action triggers in the new partition become redundant -- the parent
+    * table already has equivalent ones, and those will be able to reach the
+    * partition.  Remove the ones in the partition.  We identify them because
+    * they have our constraint OID, as well as being on the referenced rel.
     */
-   ScanKeyInit(&key,
-               Anum_pg_trigger_tgconstraint,
-               BTEqualStrategyNumber, F_OIDEQ,
-               ObjectIdGetDatum(fk->conoid));
-   scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
-                             NULL, 1, &key);
-   while ((trigtup = systable_getnext(scan)) != NULL)
-   {
-       Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
-       ObjectAddress trigger;
+   DropForeignKeyConstraintTriggers(trigrel, partConstrOid, partConstrFrelid,
+                                    partConstrRelid);
 
-       if (trgform->tgconstrrelid != fk->conrelid)
-           continue;
-       if (trgform->tgrelid != fk->confrelid)
-           continue;
-
-       /*
-        * The constraint is originally set up to contain this trigger as an
-        * implementation object, so there's a dependency record that links
-        * the two; however, since the trigger is no longer needed, we remove
-        * the dependency link in order to be able to drop the trigger while
-        * keeping the constraint intact.
-        */
-       deleteDependencyRecordsFor(TriggerRelationId,
-                                  trgform->oid,
-                                  false);
-       /* make dependency deletion visible to performDeletion */
-       CommandCounterIncrement();
-       ObjectAddressSet(trigger, TriggerRelationId,
-                        trgform->oid);
-       performDeletion(&trigger, DROP_RESTRICT, 0);
-       /* make trigger drop visible, in case the loop iterates */
-       CommandCounterIncrement();
-   }
-
-   systable_endscan(scan);
-
-   ConstraintSetParentConstraint(fk->conoid, parentConstrOid,
+   ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
                                  RelationGetRelid(partition));
 
    /*
@@ -11579,7 +11599,7 @@ tryAttachPartitionForeignKey(List **wqueue,
     * corresponding parent triggers.
     */
    GetForeignKeyCheckTriggers(trigrel,
-                              fk->conoid, fk->confrelid, fk->conrelid,
+                              partConstrOid, partConstrFrelid, partConstrRelid,
                               &insertTriggerOid, &updateTriggerOid);
    Assert(OidIsValid(insertTriggerOid) && OidIsValid(parentInsTrigger));
    TriggerSetParentTrigger(trigrel, insertTriggerOid, parentInsTrigger,
@@ -11593,72 +11613,12 @@ tryAttachPartitionForeignKey(List **wqueue,
     * attaching now has extra pg_constraint rows and action triggers that are
     * no longer needed.  Remove those.
     */
-   if (get_rel_relkind(fk->confrelid) == RELKIND_PARTITIONED_TABLE)
+   if (get_rel_relkind(partConstrFrelid) == RELKIND_PARTITIONED_TABLE)
    {
        Relation    pg_constraint = table_open(ConstraintRelationId, RowShareLock);
-       ObjectAddresses *objs;
-       HeapTuple   consttup;
-
-       ScanKeyInit(&key,
-                   Anum_pg_constraint_conrelid,
-                   BTEqualStrategyNumber, F_OIDEQ,
-                   ObjectIdGetDatum(fk->conrelid));
-
-       scan = systable_beginscan(pg_constraint,
-                                 ConstraintRelidTypidNameIndexId,
-                                 true, NULL, 1, &key);
-       objs = new_object_addresses();
-       while ((consttup = systable_getnext(scan)) != NULL)
-       {
-           Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(consttup);
-
-           if (conform->conparentid != fk->conoid)
-               continue;
-           else
-           {
-               ObjectAddress addr;
-               SysScanDesc scan2;
-               ScanKeyData key2;
-               int         n PG_USED_FOR_ASSERTS_ONLY;
 
-               ObjectAddressSet(addr, ConstraintRelationId, conform->oid);
-               add_exact_object_address(&addr, objs);
-
-               /*
-                * First we must delete the dependency record that binds the
-                * constraint records together.
-                */
-               n = deleteDependencyRecordsForSpecific(ConstraintRelationId,
-                                                      conform->oid,
-                                                      DEPENDENCY_INTERNAL,
-                                                      ConstraintRelationId,
-                                                      fk->conoid);
-               Assert(n == 1); /* actually only one is expected */
-
-               /*
-                * Now search for the triggers for this constraint and set
-                * them up for deletion too
-                */
-               ScanKeyInit(&key2,
-                           Anum_pg_trigger_tgconstraint,
-                           BTEqualStrategyNumber, F_OIDEQ,
-                           ObjectIdGetDatum(conform->oid));
-               scan2 = systable_beginscan(trigrel, TriggerConstraintIndexId,
-                                          true, NULL, 1, &key2);
-               while ((trigtup = systable_getnext(scan2)) != NULL)
-               {
-                   ObjectAddressSet(addr, TriggerRelationId,
-                                    ((Form_pg_trigger) GETSTRUCT(trigtup))->oid);
-                   add_exact_object_address(&addr, objs);
-               }
-               systable_endscan(scan2);
-           }
-       }
-       /* make the dependency deletions visible */
-       CommandCounterIncrement();
-       performMultipleDeletions(objs, DROP_RESTRICT,
-                                PERFORM_DELETION_INTERNAL);
-       systable_endscan(scan);
+       RemoveInheritedConstraint(pg_constraint, trigrel, partConstrOid,
+                                 partConstrRelid);
 
        table_close(pg_constraint, RowShareLock);
    }
@@ -11679,9 +11639,10 @@ tryAttachPartitionForeignKey(List **wqueue,
        Relation    conrel;
 
        conrel = table_open(ConstraintRelationId, RowExclusiveLock);
-       partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
+
+       partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(partConstrOid));
        if (!HeapTupleIsValid(partcontup))
-           elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
+           elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
 
        /* Use the same lock as for AT_ValidateConstraint */
        QueueFKConstraintValidation(wqueue, conrel, partition, partcontup,
@@ -11689,8 +11650,136 @@ tryAttachPartitionForeignKey(List **wqueue,
        ReleaseSysCache(partcontup);
        table_close(conrel, RowExclusiveLock);
    }
+}
 
-   return true;
+/*
+ * RemoveInheritedConstraint
+ *
+ * Removes the constraint and its associated trigger from the specified
+ * relation, which inherited the given constraint.
+ */
+static void
+RemoveInheritedConstraint(Relation conrel, Relation trigrel, Oid conoid,
+                         Oid conrelid)
+{
+   ObjectAddresses *objs;
+   HeapTuple   consttup;
+   ScanKeyData key;
+   SysScanDesc scan;
+   HeapTuple   trigtup;
+
+   ScanKeyInit(&key,
+               Anum_pg_constraint_conrelid,
+               BTEqualStrategyNumber, F_OIDEQ,
+               ObjectIdGetDatum(conrelid));
+
+   scan = systable_beginscan(conrel,
+                             ConstraintRelidTypidNameIndexId,
+                             true, NULL, 1, &key);
+   objs = new_object_addresses();
+   while ((consttup = systable_getnext(scan)) != NULL)
+   {
+       Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(consttup);
+
+       if (conform->conparentid != conoid)
+           continue;
+       else
+       {
+           ObjectAddress addr;
+           SysScanDesc scan2;
+           ScanKeyData key2;
+           int         n PG_USED_FOR_ASSERTS_ONLY;
+
+           ObjectAddressSet(addr, ConstraintRelationId, conform->oid);
+           add_exact_object_address(&addr, objs);
+
+           /*
+            * First we must delete the dependency record that binds the
+            * constraint records together.
+            */
+           n = deleteDependencyRecordsForSpecific(ConstraintRelationId,
+                                                  conform->oid,
+                                                  DEPENDENCY_INTERNAL,
+                                                  ConstraintRelationId,
+                                                  conoid);
+           Assert(n == 1);     /* actually only one is expected */
+
+           /*
+            * Now search for the triggers for this constraint and set them up
+            * for deletion too
+            */
+           ScanKeyInit(&key2,
+                       Anum_pg_trigger_tgconstraint,
+                       BTEqualStrategyNumber, F_OIDEQ,
+                       ObjectIdGetDatum(conform->oid));
+           scan2 = systable_beginscan(trigrel, TriggerConstraintIndexId,
+                                      true, NULL, 1, &key2);
+           while ((trigtup = systable_getnext(scan2)) != NULL)
+           {
+               ObjectAddressSet(addr, TriggerRelationId,
+                                ((Form_pg_trigger) GETSTRUCT(trigtup))->oid);
+               add_exact_object_address(&addr, objs);
+           }
+           systable_endscan(scan2);
+       }
+   }
+   /* make the dependency deletions visible */
+   CommandCounterIncrement();
+   performMultipleDeletions(objs, DROP_RESTRICT,
+                            PERFORM_DELETION_INTERNAL);
+   systable_endscan(scan);
+}
+
+/*
+ * DropForeignKeyConstraintTriggers
+ *
+ * The subroutine for tryAttachPartitionForeignKey handles the deletion of
+ * action triggers for the foreign key constraint.
+ */
+static void
+DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid, Oid confrelid,
+                                Oid conrelid)
+{
+   ScanKeyData key;
+   SysScanDesc scan;
+   HeapTuple   trigtup;
+
+   ScanKeyInit(&key,
+               Anum_pg_trigger_tgconstraint,
+               BTEqualStrategyNumber, F_OIDEQ,
+               ObjectIdGetDatum(conoid));
+   scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
+                             NULL, 1, &key);
+   while ((trigtup = systable_getnext(scan)) != NULL)
+   {
+       Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
+       ObjectAddress trigger;
+
+       if (trgform->tgconstrrelid != conrelid)
+           continue;
+       if (trgform->tgrelid != confrelid)
+           continue;
+
+       /*
+        * The constraint is originally set up to contain this trigger as an
+        * implementation object, so there's a dependency record that links
+        * the two; however, since the trigger is no longer needed, we remove
+        * the dependency link in order to be able to drop the trigger while
+        * keeping the constraint intact.
+        */
+       deleteDependencyRecordsFor(TriggerRelationId,
+                                  trgform->oid,
+                                  false);
+       /* make dependency deletion visible to performDeletion */
+       CommandCounterIncrement();
+       ObjectAddressSet(trigger, TriggerRelationId,
+                        trgform->oid);
+       performDeletion(&trigger, DROP_RESTRICT, 0);
+       /* make trigger drop visible, in case the loop iterates */
+       CommandCounterIncrement();
+   }
+
+   systable_endscan(scan);
 }
 
 /*