Hi,

Thanks for updating the patch.  I'll reply to other parts separately.

On 2019/03/19 7:02, Alvaro Herrera wrote:
> A pretty silly bug remains here.  Watch:
> 
> create table pk (a int primary key) partition by list (a);
> create table pk1 partition of pk for values in (1);
> create table fk (a int references pk);
> insert into pk values (1);
> insert into fk values (1);
> drop table pk cascade;
> 
> Note that the DROP of the main PK table is pretty aggressive: since it
> cascades, you want it to drop the constraint on the FK side.  This would
> work without a problem if 'pk' was a non-partitioned table, but in this
> case it fails:
> 
> alvherre=# drop table pk cascade;
> NOTICE:  drop cascades to constraint fk_a_fkey on table fk
> ERROR:  removing partition "pk1" violates foreign key constraint "fk_a_fkey1"
> DETALLE:  Key (a)=(1) still referenced from table "fk".
> 
> The reason is that the "pre drop check" that's done before allow a drop
> of the partition doesn't realize that the constraint is also being
> dropped (and so the check ought to be skipped).  If you were to do just
> "DROP TABLE pk1" then this complaint would be correct, since it would
> leave the constraint in an invalid state.  But here, it's bogus and
> annoying.  You can DELETE the matching values from table FK and then the
> DROP works.  Here's another problem caused by the same misbehavior:
> 
> alvherre=# drop table pk, fk;
> ERROR:  removing partition "pk1" violates foreign key constraint "fk_a_fkey1"
> DETALLE:  Key (a)=(1) still referenced from table "fk".
> 
> Note here we want to get rid of table 'fk' completely.  If you split it
> up in a DROP of fk, followed by a DROP of pk, it works.
> 
> I'm not yet sure what's the best way to attack this.  Maybe the
> "pre-drop check" needs a completely different approach.  The simplest
> approach is to prohibit a table drop or detach for any partitioned table
> that's referenced by a foreign key, but that seems obnoxious and
> inconvenient.

Agreed.

Will it suffice or be OK if we skipped invoking the pre-drop callback for
objects that are being "indirectly" dropped?  I came up with the attached
patch to resolve this problem, if that idea has any merit.  We also get
slightly better error message as seen the expected/foreign_key.out changes.

Thanks,
Amit
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 11ec9d2f85..223ff9e4d8 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -237,17 +237,15 @@ deleteObjectsInList(ObjectAddresses *targetObjects, 
Relation *depRel,
        {
                const ObjectAddress *thisobj = &targetObjects->refs[i];
                Oid                     objectClass = getObjectClass(thisobj);
+               const ObjectAddressExtra *extra = &targetObjects->extras[i];
+               bool            original = extra->flags & DEPFLAG_ORIGINAL;
 
                if (trackDroppedObjectsNeeded() && !(flags & 
PERFORM_DELETION_INTERNAL))
                {
                        if (EventTriggerSupportsObjectClass(objectClass))
                        {
-                               bool            original = false;
                                bool            normal = false;
-                               const ObjectAddressExtra *extra = 
&targetObjects->extras[i];
 
-                               if (extra->flags & DEPFLAG_ORIGINAL)
-                                       original = true;
                                if (extra->flags & DEPFLAG_NORMAL ||
                                        extra->flags & DEPFLAG_REVERSE)
                                        normal = true;
@@ -256,6 +254,13 @@ deleteObjectsInList(ObjectAddresses *targetObjects, 
Relation *depRel,
                        }
                }
 
+               /*
+                * Do not invoke the callback for objects that are being dropped
+                * indirectly.
+                */
+               if (!original)
+                       continue;
+
                /* Invoke class-specific pre-deletion checks */
                switch (objectClass)
                {
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 6680f755dd..9305ba8017 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1837,14 +1837,11 @@ pre_drop_class_check(Oid relationId, Oid objectSubId)
        relation = relation_open(relationId, NoLock);
 
        /*
-        * For leaf partitions, this is our last chance to verify any foreign 
keys
+        * For partitions, this is our last chance to verify any foreign keys
         * that may point to the partition as referenced table.
         */
-       if (relation->rd_rel->relkind == RELKIND_RELATION &&
-               relation->rd_rel->relispartition)
-               CheckNoForeignKeyRefs(relation,
-                                                         
GetParentedForeignKeyRefs(relation),
-                                                         true);
+       if (relation->rd_rel->relispartition)
+               CheckNoForeignKeyRefs(relation, 
GetParentedForeignKeyRefs(relation));
 
        relation_close(relation, NoLock);
 }
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8e26053a5c..ac4378db09 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -15768,7 +15768,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
        /* Ensure that foreign keys still hold after this detach */
        refconstraints = GetParentedForeignKeyRefs(partRel);
-       CheckNoForeignKeyRefs(partRel, refconstraints, false);
+       CheckNoForeignKeyRefs(partRel, refconstraints);
 
        /* All inheritance related checks are performed within the function */
        RemoveInheritance(partRel, rel);
@@ -16348,21 +16348,10 @@ GetParentedForeignKeyRefs(Relation partition)
  * Returns a list of such constraints.
  */
 void
-CheckNoForeignKeyRefs(Relation partition, List *constraints, bool isDrop)
+CheckNoForeignKeyRefs(Relation partition, List *constraints)
 {
        ListCell   *cell;
 
-       /*
-        * In the DROP case, we can skip this check when this is a partitioned
-        * partition, because its partitions will go through this also, and we'd
-        * run the check twice uselessly.
-        *
-        * In the DETACH case, this is only called for the top-level relation, 
so
-        * we must run it nevertheless.
-        */
-       if (isDrop && partition->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-               return;
-
        foreach(cell, constraints)
        {
                Oid                     constrOid = lfirst_oid(cell);
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index efbb5c3a93..03783abc6d 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -53,8 +53,7 @@ extern void AlterRelationNamespaceInternal(Relation classRel, 
Oid relOid,
 extern void CheckTableNotInUse(Relation rel, const char *stmt);
 
 extern List *GetParentedForeignKeyRefs(Relation partition);
-extern void CheckNoForeignKeyRefs(Relation partition, List *constraints,
-                                         bool isDrop);
+extern void CheckNoForeignKeyRefs(Relation partition, List *constraints);
 
 extern void ExecuteTruncate(TruncateStmt *stmt);
 extern void ExecuteTruncateGuts(List *explicit_rels, List *relids, List 
*relids_logged,
diff --git a/src/test/regress/expected/foreign_key.out 
b/src/test/regress/expected/foreign_key.out
index 810a689ab5..dd88167062 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -2143,8 +2143,8 @@ drop table droppk1;
 ERROR:  removing partition "droppk1" violates foreign key constraint 
"dropfk_a_fkey1"
 DETAIL:  Key (a)=(1) still referenced from table "dropfk".
 drop table droppk2;
-ERROR:  removing partition "droppk2_d" violates foreign key constraint 
"dropfk_a_fkey4"
-DETAIL:  Key (a)=(1500) still referenced from table "dropfk".
+ERROR:  removing partition "droppk2" violates foreign key constraint 
"dropfk_a_fkey2"
+DETAIL:  Key (a)=(1000) still referenced from table "dropfk".
 drop table droppk21;
 ERROR:  removing partition "droppk21" violates foreign key constraint 
"dropfk_a_fkey3"
 DETAIL:  Key (a)=(1000) still referenced from table "dropfk".

Reply via email to