diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 6dd0700da75..0fd792ff1a2 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -2970,7 +2970,7 @@ SCRAM-SHA-256$<iteration count>:&l referenced object, and should be automatically dropped (regardless of RESTRICT or CASCADE mode) if the referenced object is dropped. Example: a named - constraint on a table is made autodependent on the table, so + constraint on a table is made auto-dependent on the table, so that it will go away if the table is dropped. @@ -2982,38 +2982,61 @@ SCRAM-SHA-256$<iteration count>:&l The dependent object was created as part of creation of the referenced object, and is really just a part of its internal - implementation. A DROP of the dependent object - will be disallowed outright (we'll tell the user to issue a - DROP against the referenced object, instead). A - DROP of the referenced object will be propagated - through to drop the dependent object whether - CASCADE is specified or not. Example: a trigger - that's created to enforce a foreign-key constraint is made - internally dependent on the constraint's - pg_constraint entry. + implementation. A direct DROP of the dependent + object will be disallowed outright (we'll tell the user to issue + a DROP against the referenced object, instead). + A DROP of the referenced object will result in + automatically dropping the dependent object + whether CASCADE is specified or not. If the + dependent object is reached due to a dependency on some other object, + the drop is converted to a drop of the referenced object, so + that NORMAL and AUTO + dependencies of the dependent object behave much like they were + dependencies of the referenced object. + Example: a view's ON SELECT rule is made + internally dependent on the view, preventing it from being dropped + while the view remains. Dependencies of the rule (such as tables it + refers to) act as if they were dependencies of the view. - DEPENDENCY_INTERNAL_AUTO (I) + DEPENDENCY_PARTITION_PRI (P) + DEPENDENCY_PARTITION_SEC (S) The dependent object was created as part of creation of the referenced object, and is really just a part of its internal - implementation. A DROP of the dependent object - will be disallowed outright (we'll tell the user to issue a - DROP against the referenced object, instead). - While a regular internal dependency will prevent - the dependent object from being dropped while any such dependencies - remain, DEPENDENCY_INTERNAL_AUTO will allow such - a drop as long as the object can be found by following any of such + implementation; however, unlike INTERNAL, + there is more than one such referenced object. The dependent object + must not be dropped unless at least one of these referenced objects + is dropped; if any one is, the dependent object should be dropped + whether or not CASCADE is specified. Also + unlike INTERNAL, a drop of some other object + that the dependent object depends on does not result in automatic + deletion of any partition-referenced object. Hence, if the drop + does not cascade to at least one of these objects via some other + path, it will be refused. (In most cases, the dependent object + shares all its non-partition dependencies with at least one + partition-referenced object, so that this restriction does not + result in blocking any cascaded delete.) + Primary and secondary partition dependencies behave identically + except that the primary dependency is preferred for use in error + messages; hence, a partition-dependent object should have one + primary partition dependency and one or more secondary partition dependencies. - Example: an index on a partition is made internal-auto-dependent on - both the partition itself as well as on the index on the parent - partitioned table; so the partition index is dropped together with - either the partition it indexes, or with the parent index it is - attached to. + Note that partition dependencies are made in addition to, not + instead of, any dependencies the object would normally have. This + simplifies ATTACH/DETACH PARTITION operations: + the partition dependencies need only be added or removed. + Example: a child partitioned index is made partition-dependent + on both the partition table it is on and the parent partitioned + index, so that it goes away if either of those is dropped, but + not otherwise. The dependency on the parent index is primary, + so that if the user tries to drop the child partitioned index, + the error message will suggest dropping the parent index instead + (not the table). @@ -3026,9 +3049,10 @@ SCRAM-SHA-256$<iteration count>:&l the referenced object (see pg_extension). The dependent object can be dropped only via - DROP EXTENSION on the referenced object. Functionally - this dependency type acts the same as an internal dependency, but - it's kept separate for clarity and to simplify pg_dump. + DROP EXTENSION on the referenced object. + Functionally this dependency type acts the same as + an INTERNAL dependency, but it's kept separate for + clarity and to simplify pg_dump. @@ -3038,10 +3062,13 @@ SCRAM-SHA-256$<iteration count>:&l The dependent object is not a member of the extension that is the - referenced object (and so should not be ignored by pg_dump), but - cannot function without it and should be dropped when the - extension itself is. The dependent object may be dropped on its - own as well. + referenced object (and so it should not be ignored + by pg_dump), but it cannot function + without the extension and should be auto-dropped if the extension is. + The dependent object may be dropped on its own as well. + Functionally this dependency type acts the same as + an AUTO dependency, but it's kept separate for + clarity and to simplify pg_dump. @@ -3063,6 +3090,19 @@ SCRAM-SHA-256$<iteration count>:&l Other dependency flavors might be needed in future. + + Note that it's quite possible for two objects to be linked by more than + one pg_depend entry. For example, a child + partitioned index would have both a partition-type dependency on its + associated partition table, and an auto dependency on each column of + that table that it indexes. This sort of situation expresses the union + of multiple dependency semantics. A dependent object can be dropped + without CASCADE if any of its dependencies satisfies + its condition for automatic dropping. Conversely, all the + dependencies' restrictions about which objects must be dropped together + must be satisfied. + + diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 2c548958313..2048d71535b 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -99,9 +99,11 @@ typedef struct #define DEPFLAG_NORMAL 0x0002 /* reached via normal dependency */ #define DEPFLAG_AUTO 0x0004 /* reached via auto dependency */ #define DEPFLAG_INTERNAL 0x0008 /* reached via internal dependency */ -#define DEPFLAG_EXTENSION 0x0010 /* reached via extension dependency */ -#define DEPFLAG_REVERSE 0x0020 /* reverse internal/extension link */ -#define DEPFLAG_SUBOBJECT 0x0040 /* subobject of another deletable object */ +#define DEPFLAG_PARTITION 0x0010 /* reached via partition dependency */ +#define DEPFLAG_EXTENSION 0x0020 /* reached via extension dependency */ +#define DEPFLAG_REVERSE 0x0040 /* reverse internal/extension link */ +#define DEPFLAG_IS_PART 0x0080 /* has a partition dependency */ +#define DEPFLAG_SUBOBJECT 0x0100 /* subobject of another deletable object */ /* expansible list of ObjectAddresses */ @@ -478,6 +480,8 @@ findDependentObjects(const ObjectAddress *object, SysScanDesc scan; HeapTuple tup; ObjectAddress otherObject; + ObjectAddress owningObject; + ObjectAddress partitionObject; ObjectAddressAndFlags *dependentObjects; int numDependentObjects; int maxDependentObjects; @@ -547,6 +551,10 @@ findDependentObjects(const ObjectAddress *object, scan = systable_beginscan(*depRel, DependDependerIndexId, true, NULL, nkeys, key); + /* initialize variables that loop may fill */ + memset(&owningObject, 0, sizeof(owningObject)); + memset(&partitionObject, 0, sizeof(partitionObject)); + while (HeapTupleIsValid(tup = systable_getnext(scan))) { Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup); @@ -591,24 +599,26 @@ findDependentObjects(const ObjectAddress *object, /* FALL THRU */ case DEPENDENCY_INTERNAL: - case DEPENDENCY_INTERNAL_AUTO: /* * This object is part of the internal implementation of * another object, or is part of the extension that is the * other object. We have three cases: * - * 1. At the outermost recursion level, disallow the DROP. (We - * just ereport here, rather than proceeding, since no other - * dependencies are likely to be interesting.) However, if - * the owning object is listed in pendingObjects, just release - * the caller's lock and return; we'll eventually complete the - * DROP when we reach that entry in the pending list. + * 1. At the outermost recursion level, we must disallow the + * DROP. However, if the owning object is listed in + * pendingObjects, just release the caller's lock and return; + * we'll eventually complete the DROP when we reach that entry + * in the pending list. + * + * Note: the above statement is true only if this pg_depend + * entry still exists by then; in principle, therefore, we + * could miss deleting an item the user told us to delete. + * However, no inconsistency can result: since we're at outer + * level, there is no object depending on this one. */ if (stack == NULL) { - char *otherObjDesc; - if (pendingObjects && object_address_present(&otherObject, pendingObjects)) { @@ -617,14 +627,21 @@ findDependentObjects(const ObjectAddress *object, ReleaseDeletionLock(object); return; } - otherObjDesc = getObjectDescription(&otherObject); - ereport(ERROR, - (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), - errmsg("cannot drop %s because %s requires it", - getObjectDescription(object), - otherObjDesc), - errhint("You can drop %s instead.", - otherObjDesc))); + + /* + * We postpone actually issuing the error message until + * after this loop, so that we can make the behavior + * independent of the ordering of pg_depend entries, at + * least if there's not more than one INTERNAL and one + * EXTENSION dependency. (If there's more, we'll complain + * about a random one of them.) Prefer to complain about + * EXTENSION, since that's generally a more important + * dependency. + */ + if (!OidIsValid(owningObject.classId) || + foundDep->deptype == DEPENDENCY_EXTENSION) + owningObject = otherObject; + break; } /* @@ -643,14 +660,6 @@ findDependentObjects(const ObjectAddress *object, * transform this deletion request into a delete of this * owning object. * - * For INTERNAL_AUTO dependencies, we don't enforce this; in - * other words, we don't follow the links back to the owning - * object. - */ - if (foundDep->deptype == DEPENDENCY_INTERNAL_AUTO) - break; - - /* * First, release caller's lock on this object and get * deletion lock on the owning object. (We must release * caller's lock to avoid deadlock against a concurrent @@ -672,6 +681,13 @@ findDependentObjects(const ObjectAddress *object, return; } + /* + * One way or the other, we're done with the scan; might as + * well close it down before recursing, to reduce peak + * resource consumption. + */ + systable_endscan(scan); + /* * Okay, recurse to the owning object instead of proceeding. * @@ -690,10 +706,66 @@ findDependentObjects(const ObjectAddress *object, targetObjects, pendingObjects, depRel); + + /* + * The current target object should have been added to + * targetObjects while processing the owning object; but it + * probably got only the flag bits associated with the + * dependency we're looking at. We need to add the objflags + * that were passed to this recursion level, too, else we may + * get a bogus failure in reportDependentObjects (if, for + * example, we were called due to a partition dependency). + * + * If somehow the current object didn't get scheduled for + * deletion, bleat. (That would imply that somebody deleted + * this dependency record before the recursion got to it.) + * Another idea would be to reacquire lock on the current + * object and resume trying to delete it, but it seems not + * worth dealing with the race conditions inherent in that. + */ + if (!object_address_present_add_flags(object, objflags, + targetObjects)) + elog(ERROR, "deletion of owning object %s failed to delete %s", + getObjectDescription(&otherObject), + getObjectDescription(object)); + /* And we're done here. */ - systable_endscan(scan); return; + case DEPENDENCY_PARTITION_PRI: + + /* + * Remember that this object has a partition-type dependency. + * After the dependency scan, we'll complain if we didn't find + * a reason to delete one of its partition dependencies. + */ + objflags |= DEPFLAG_IS_PART; + + /* + * Also remember the primary partition owner, for error + * messages. If there are multiple primary owners (which + * there should not be), we'll report a random one of them. + */ + partitionObject = otherObject; + break; + + case DEPENDENCY_PARTITION_SEC: + + /* + * Only use secondary partition owners in error messages if we + * find no primary owner (which probably shouldn't happen). + */ + if (!(objflags & DEPFLAG_IS_PART)) + partitionObject = otherObject; + + /* + * Remember that this object has a partition-type dependency. + * After the dependency scan, we'll complain if we didn't find + * a reason to delete one of its partition dependencies. + */ + objflags |= DEPFLAG_IS_PART; + break; + case DEPENDENCY_PIN: /* @@ -712,6 +784,28 @@ findDependentObjects(const ObjectAddress *object, systable_endscan(scan); + /* + * If we found an INTERNAL or EXTENSION dependency when we're at outer + * level, complain about it now. If we also found a PARTITION dependency, + * we prefer to report the PARTITION dependency. This is arbitrary but + * seems to be more useful in practice. + */ + if (OidIsValid(owningObject.classId)) + { + char *otherObjDesc; + + if (OidIsValid(partitionObject.classId)) + otherObjDesc = getObjectDescription(&partitionObject); + else + otherObjDesc = getObjectDescription(&owningObject); + + ereport(ERROR, + (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), + errmsg("cannot drop %s because %s requires it", + getObjectDescription(object), otherObjDesc), + errhint("You can drop %s instead.", otherObjDesc))); + } + /* * Next, identify all objects that directly depend on the current object. * To ensure predictable deletion order, we collect them up in @@ -789,10 +883,13 @@ findDependentObjects(const ObjectAddress *object, case DEPENDENCY_AUTO_EXTENSION: subflags = DEPFLAG_AUTO; break; - case DEPENDENCY_INTERNAL_AUTO: case DEPENDENCY_INTERNAL: subflags = DEPFLAG_INTERNAL; break; + case DEPENDENCY_PARTITION_PRI: + case DEPENDENCY_PARTITION_SEC: + subflags = DEPFLAG_PARTITION; + break; case DEPENDENCY_EXTENSION: subflags = DEPFLAG_EXTENSION; break; @@ -868,10 +965,15 @@ findDependentObjects(const ObjectAddress *object, /* * Finally, we can add the target object to targetObjects. Be careful to * include any flags that were passed back down to us from inner recursion - * levels. + * levels. Record the "dependee" as being either the most important + * partition owner if there is one, else the object we recursed from, if + * any. (The logic in reportDependentObjects() is such that it can only + * need one of those objects.) */ extra.flags = mystack.flags; - if (stack) + if (extra.flags & DEPFLAG_IS_PART) + extra.dependee = partitionObject; + else if (stack) extra.dependee = *stack->object; else memset(&extra.dependee, 0, sizeof(extra.dependee)); @@ -905,9 +1007,38 @@ reportDependentObjects(const ObjectAddresses *targetObjects, int numNotReportedClient = 0; int i; + /* + * If we need to delete any partition-dependent objects, make sure that + * we're deleting at least one of their partition dependencies, too. That + * can be detected by checking that we reached them by a PARTITION + * dependency at some point. + * + * We just report the first such object, as in most cases the only way to + * trigger this complaint is to explicitly try to delete one partition of + * a partitioned object. + */ + for (i = 0; i < targetObjects->numrefs; i++) + { + const ObjectAddressExtra *extra = &targetObjects->extras[i]; + + if ((extra->flags & DEPFLAG_IS_PART) && + !(extra->flags & DEPFLAG_PARTITION)) + { + const ObjectAddress *object = &targetObjects->refs[i]; + char *otherObjDesc = getObjectDescription(&extra->dependee); + + ereport(ERROR, + (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), + errmsg("cannot drop %s because %s requires it", + getObjectDescription(object), otherObjDesc), + errhint("You can drop %s instead.", otherObjDesc))); + } + } + /* * If no error is to be thrown, and the msglevel is too low to be shown to - * either client or server log, there's no need to do any of the work. + * either client or server log, there's no need to do any of the rest of + * the work. * * Note: this code doesn't know all there is to be known about elog * levels, but it works for NOTICE and DEBUG2, which are the only values @@ -951,11 +1082,12 @@ reportDependentObjects(const ObjectAddresses *targetObjects, /* * If, at any stage of the recursive search, we reached the object via - * an AUTO, INTERNAL, or EXTENSION dependency, then it's okay to - * delete it even in RESTRICT mode. + * an AUTO, INTERNAL, PARTITION, or EXTENSION dependency, then it's + * okay to delete it even in RESTRICT mode. */ if (extra->flags & (DEPFLAG_AUTO | DEPFLAG_INTERNAL | + DEPFLAG_PARTITION | DEPFLAG_EXTENSION)) { /* diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index faf69568133..d16c3d0ea50 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -1041,9 +1041,6 @@ index_create(Relation heapRelation, else { bool have_simple_col = false; - DependencyType deptype; - - deptype = OidIsValid(parentIndexRelid) ? DEPENDENCY_INTERNAL_AUTO : DEPENDENCY_AUTO; /* Create auto dependencies on simply-referenced columns */ for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++) @@ -1054,7 +1051,7 @@ index_create(Relation heapRelation, referenced.objectId = heapRelationId; referenced.objectSubId = indexInfo->ii_IndexAttrNumbers[i]; - recordDependencyOn(&myself, &referenced, deptype); + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); have_simple_col = true; } @@ -1072,18 +1069,29 @@ index_create(Relation heapRelation, referenced.objectId = heapRelationId; referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, deptype); + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); } } - /* Store dependency on parent index, if any */ + /* + * If this is an index partition, create partition dependencies on + * both the parent index and the table. (Note: these must be *in + * addition to*, not instead of, all other dependencies. Otherwise + * we'll be short some dependencies after DETACH PARTITION.) + */ if (OidIsValid(parentIndexRelid)) { referenced.classId = RelationRelationId; referenced.objectId = parentIndexRelid; referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL_AUTO); + recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_PRI); + + referenced.classId = RelationRelationId; + referenced.objectId = heapRelationId; + referenced.objectSubId = 0; + + recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC); } /* Store dependency on collations */ @@ -1342,15 +1350,17 @@ index_constraint_create(Relation heapRelation, recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL); /* - * Also, if this is a constraint on a partition, mark it as depending on - * the constraint in the parent. + * Also, if this is a constraint on a partition, give it partition-type + * dependencies on the parent constraint as well as the table. */ if (OidIsValid(parentConstraintId)) { - ObjectAddress parentConstr; - - ObjectAddressSet(parentConstr, ConstraintRelationId, parentConstraintId); - recordDependencyOn(&referenced, &parentConstr, DEPENDENCY_INTERNAL_AUTO); + ObjectAddressSet(myself, ConstraintRelationId, conOid); + ObjectAddressSet(referenced, ConstraintRelationId, parentConstraintId); + recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_PRI); + ObjectAddressSet(referenced, RelationRelationId, + RelationGetRelid(heapRelation)); + recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC); } /* diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 698b493fc40..ad836e0d98e 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -760,13 +760,17 @@ AlterConstraintNamespaces(Oid ownerId, Oid oldNspId, /* * ConstraintSetParentConstraint - * Set a partition's constraint as child of its parent table's + * Set a partition's constraint as child of its parent constraint, + * or remove the linkage if parentConstrId is InvalidOid. * * This updates the constraint's pg_constraint row to show it as inherited, and - * add a dependency to the parent so that it cannot be removed on its own. + * adds PARTITION dependencies to prevent the constraint from being deleted + * on its own. Alternatively, reverse that. */ void -ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId) +ConstraintSetParentConstraint(Oid childConstrId, + Oid parentConstrId, + Oid childTableId) { Relation constrRel; Form_pg_constraint constrForm; @@ -795,10 +799,13 @@ ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId) CatalogTupleUpdate(constrRel, &tuple->t_self, newtup); - ObjectAddressSet(referenced, ConstraintRelationId, parentConstrId); ObjectAddressSet(depender, ConstraintRelationId, childConstrId); - recordDependencyOn(&depender, &referenced, DEPENDENCY_INTERNAL_AUTO); + ObjectAddressSet(referenced, ConstraintRelationId, parentConstrId); + recordDependencyOn(&depender, &referenced, DEPENDENCY_PARTITION_PRI); + + ObjectAddressSet(referenced, RelationRelationId, childTableId); + recordDependencyOn(&depender, &referenced, DEPENDENCY_PARTITION_SEC); } else { @@ -809,10 +816,14 @@ ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId) /* Make sure there's no further inheritance. */ Assert(constrForm->coninhcount == 0); + CatalogTupleUpdate(constrRel, &tuple->t_self, newtup); + deleteDependencyRecordsForClass(ConstraintRelationId, childConstrId, ConstraintRelationId, - DEPENDENCY_INTERNAL_AUTO); - CatalogTupleUpdate(constrRel, &tuple->t_self, newtup); + DEPENDENCY_PARTITION_PRI); + deleteDependencyRecordsForClass(ConstraintRelationId, childConstrId, + RelationRelationId, + DEPENDENCY_PARTITION_SEC); } ReleaseSysCache(tuple); diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index bd85099c286..7352b9e341d 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -971,7 +971,8 @@ DefineIndex(Oid relationId, IndexSetParentIndex(cldidx, indexRelationId); if (createdConstraintId != InvalidOid) ConstraintSetParentConstraint(cldConstrOid, - createdConstraintId); + createdConstraintId, + childRelid); if (!cldidx->rd_index->indisvalid) invalidate_parent = true; @@ -2622,35 +2623,34 @@ IndexSetParentIndex(Relation partitionIdx, Oid parentOid) if (fix_dependencies) { - ObjectAddress partIdx; - /* - * Insert/delete pg_depend rows. If setting a parent, add an - * INTERNAL_AUTO dependency to the parent index; if making standalone, - * remove all existing rows and put back the regular dependency on the - * table. + * Insert/delete pg_depend rows. If setting a parent, add PARTITION + * dependencies on the parent index and the table; if removing a + * parent, delete PARTITION dependencies. */ - ObjectAddressSet(partIdx, RelationRelationId, partRelid); - if (OidIsValid(parentOid)) { + ObjectAddress partIdx; ObjectAddress parentIdx; + ObjectAddress partitionTbl; + ObjectAddressSet(partIdx, RelationRelationId, partRelid); ObjectAddressSet(parentIdx, RelationRelationId, parentOid); - recordDependencyOn(&partIdx, &parentIdx, DEPENDENCY_INTERNAL_AUTO); + ObjectAddressSet(partitionTbl, RelationRelationId, + partitionIdx->rd_index->indrelid); + recordDependencyOn(&partIdx, &parentIdx, + DEPENDENCY_PARTITION_PRI); + recordDependencyOn(&partIdx, &partitionTbl, + DEPENDENCY_PARTITION_SEC); } else { - ObjectAddress partitionTbl; - - ObjectAddressSet(partitionTbl, RelationRelationId, - partitionIdx->rd_index->indrelid); - deleteDependencyRecordsForClass(RelationRelationId, partRelid, RelationRelationId, - DEPENDENCY_INTERNAL_AUTO); - - recordDependencyOn(&partIdx, &partitionTbl, DEPENDENCY_AUTO); + DEPENDENCY_PARTITION_PRI); + deleteDependencyRecordsForClass(RelationRelationId, partRelid, + RelationRelationId, + DEPENDENCY_PARTITION_SEC); } /* make our updates visible */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index b66d194b80d..715c6a221cf 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -7825,7 +7825,8 @@ CloneFkReferencing(Relation pg_constraint, Relation parentRel, bool attach_it; Oid constrOid; ObjectAddress parentAddr, - childAddr; + childAddr, + childTableAddr; ListCell *cell; int i; @@ -7966,7 +7967,8 @@ CloneFkReferencing(Relation pg_constraint, Relation parentRel, systable_endscan(scan); table_close(trigrel, RowExclusiveLock); - ConstraintSetParentConstraint(fk->conoid, parentConstrOid); + ConstraintSetParentConstraint(fk->conoid, parentConstrOid, + RelationGetRelid(partRel)); CommandCounterIncrement(); attach_it = true; break; @@ -8013,8 +8015,14 @@ CloneFkReferencing(Relation pg_constraint, Relation parentRel, 1, false, true); subclone = lappend_oid(subclone, constrOid); + /* Set up partition dependencies for the new constraint */ ObjectAddressSet(childAddr, ConstraintRelationId, constrOid); - recordDependencyOn(&childAddr, &parentAddr, DEPENDENCY_INTERNAL_AUTO); + recordDependencyOn(&childAddr, &parentAddr, + DEPENDENCY_PARTITION_PRI); + ObjectAddressSet(childTableAddr, RelationRelationId, + RelationGetRelid(partRel)); + recordDependencyOn(&childAddr, &childTableAddr, + DEPENDENCY_PARTITION_SEC); fkconstraint = makeNode(Constraint); /* for now this is all we need */ @@ -14893,7 +14901,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel) /* bingo. */ IndexSetParentIndex(attachrelIdxRels[i], idx); if (OidIsValid(constraintOid)) - ConstraintSetParentConstraint(cldConstrOid, constraintOid); + ConstraintSetParentConstraint(cldConstrOid, constraintOid, + RelationGetRelid(attachrel)); update_relispartition(NULL, cldIdxId, true); found = true; break; @@ -15151,7 +15160,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name) constrOid = get_relation_idx_constraint_oid(RelationGetRelid(partRel), idxid); if (OidIsValid(constrOid)) - ConstraintSetParentConstraint(constrOid, InvalidOid); + ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid); index_close(idx, NoLock); } @@ -15183,7 +15192,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name) } /* unset conparentid and adjust conislocal, coninhcount, etc. */ - ConstraintSetParentConstraint(fk->conoid, InvalidOid); + ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid); /* * Make the action triggers on the referenced relation. When this was @@ -15419,7 +15428,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name) /* All good -- do it */ IndexSetParentIndex(partIdx, RelationGetRelid(parentIdx)); if (OidIsValid(constraintOid)) - ConstraintSetParentConstraint(cldConstrId, constraintOid); + ConstraintSetParentConstraint(cldConstrId, constraintOid, + RelationGetRelid(partTbl)); update_relispartition(NULL, partIdxId, true); pfree(attmap); diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 0b245a613e0..409bee24f89 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -1012,17 +1012,11 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, * User CREATE TRIGGER, so place dependencies. We make trigger be * auto-dropped if its relation is dropped or if the FK relation is * dropped. (Auto drop is compatible with our pre-7.3 behavior.) - * - * Exception: if this trigger comes from a parent partitioned table, - * then it's not separately drop-able, but goes away if the partition - * does. */ referenced.classId = RelationRelationId; referenced.objectId = RelationGetRelid(rel); referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, OidIsValid(parentTriggerOid) ? - DEPENDENCY_INTERNAL_AUTO : - DEPENDENCY_AUTO); + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); if (OidIsValid(constrrelid)) { @@ -1046,11 +1040,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, recordDependencyOn(&referenced, &myself, DEPENDENCY_INTERNAL); } - /* Depends on the parent trigger, if there is one. */ + /* + * If it's a partition trigger, create the partition dependencies. + */ if (OidIsValid(parentTriggerOid)) { ObjectAddressSet(referenced, TriggerRelationId, parentTriggerOid); - recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL_AUTO); + recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_PRI); + ObjectAddressSet(referenced, RelationRelationId, RelationGetRelid(rel)); + recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC); } } diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 968a6800ddc..7a65fde762b 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201902092 +#define CATALOG_VERSION_NO 201902111 #endif diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 5dea27016eb..b235a23f5dc 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -24,64 +24,8 @@ * * In all cases, a dependency relationship indicates that the referenced * object may not be dropped without also dropping the dependent object. - * However, there are several subflavors: - * - * DEPENDENCY_NORMAL ('n'): normal relationship between separately-created - * objects. The dependent object may be dropped without affecting the - * referenced object. The referenced object may only be dropped by - * specifying CASCADE, in which case the dependent object is dropped too. - * Example: a table column has a normal dependency on its datatype. - * - * DEPENDENCY_AUTO ('a'): the dependent object can be dropped separately - * from the referenced object, and should be automatically dropped - * (regardless of RESTRICT or CASCADE mode) if the referenced object - * is dropped. - * Example: a named constraint on a table is made auto-dependent on - * the table, so that it will go away if the table is dropped. - * - * DEPENDENCY_INTERNAL ('i'): the dependent object was created as part - * of creation of the referenced object, and is really just a part of - * its internal implementation. A DROP of the dependent object will be - * disallowed outright (we'll tell the user to issue a DROP against the - * referenced object, instead). A DROP of the referenced object will be - * propagated through to drop the dependent object whether CASCADE is - * specified or not. - * Example: a trigger that's created to enforce a foreign-key constraint - * is made internally dependent on the constraint's pg_constraint entry. - * - * DEPENDENCY_INTERNAL_AUTO ('I'): the dependent object was created as - * part of creation of the referenced object, and is really just a part - * of its internal implementation. A DROP of the dependent object will - * be disallowed outright (we'll tell the user to issue a DROP against the - * referenced object, instead). While a regular internal dependency will - * prevent the dependent object from being dropped while any such - * dependencies remain, DEPENDENCY_INTERNAL_AUTO will allow such a drop as - * long as the object can be found by following any of such dependencies. - * Example: an index on a partition is made internal-auto-dependent on - * both the partition itself as well as on the index on the parent - * partitioned table; so the partition index is dropped together with - * either the partition it indexes, or with the parent index it is attached - * to. - - * DEPENDENCY_EXTENSION ('e'): the dependent object is a member of the - * extension that is the referenced object. The dependent object can be - * dropped only via DROP EXTENSION on the referenced object. Functionally - * this dependency type acts the same as an internal dependency, but it's - * kept separate for clarity and to simplify pg_dump. - * - * DEPENDENCY_AUTO_EXTENSION ('x'): the dependent object is not a member - * of the extension that is the referenced object (and so should not be - * ignored by pg_dump), but cannot function without the extension and - * should be dropped when the extension itself is. The dependent object - * may be dropped on its own as well. - * - * DEPENDENCY_PIN ('p'): there is no dependent object; this type of entry - * is a signal that the system itself depends on the referenced object, - * and so that object must never be deleted. Entries of this type are - * created only during initdb. The fields for the dependent object - * contain zeroes. - * - * Other dependency flavors may be needed in future. + * However, there are several subflavors; see the description of pg_depend + * in catalogs.sgml for details. */ typedef enum DependencyType @@ -89,7 +33,8 @@ typedef enum DependencyType DEPENDENCY_NORMAL = 'n', DEPENDENCY_AUTO = 'a', DEPENDENCY_INTERNAL = 'i', - DEPENDENCY_INTERNAL_AUTO = 'I', + DEPENDENCY_PARTITION_PRI = 'P', + DEPENDENCY_PARTITION_SEC = 'S', DEPENDENCY_EXTENSION = 'e', DEPENDENCY_AUTO_EXTENSION = 'x', DEPENDENCY_PIN = 'p' diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h index 55a2694b101..c87bdedbbbe 100644 --- a/src/include/catalog/pg_constraint.h +++ b/src/include/catalog/pg_constraint.h @@ -239,7 +239,8 @@ extern char *ChooseConstraintName(const char *name1, const char *name2, extern void AlterConstraintNamespaces(Oid ownerId, Oid oldNspId, Oid newNspId, bool isType, ObjectAddresses *objsMoved); extern void ConstraintSetParentConstraint(Oid childConstrId, - Oid parentConstrId); + Oid parentConstrId, + Oid childTableId); extern Oid get_relation_constraint_oid(Oid relid, const char *conname, bool missing_ok); extern Bitmapset *get_relation_constraint_attnos(Oid relid, const char *conname, bool missing_ok, Oid *constraintOid); diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out index b344351ee7f..1f74cc4d448 100644 --- a/src/test/regress/expected/indexing.out +++ b/src/test/regress/expected/indexing.out @@ -528,6 +528,30 @@ select relname, relkind from pg_class where relname like 'idxpart%' order by rel ---------+--------- (0 rows) +create table idxpart (a int, b int, c int) partition by range(a); +create index on idxpart(c); +create table idxpart1 partition of idxpart for values from (0) to (250); +create table idxpart2 partition of idxpart for values from (250) to (500); +alter table idxpart detach partition idxpart2; +\d idxpart2 + Table "public.idxpart2" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | + c | integer | | | +Indexes: + "idxpart2_c_idx" btree (c) + +alter table idxpart2 drop column c; +\d idxpart2 + Table "public.idxpart2" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | + +drop table idxpart, idxpart2; -- Verify that expression indexes inherit correctly create table idxpart (a int, b int) partition by range (a); create table idxpart1 (like idxpart); diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql index 3d43a57da22..42b4b03b817 100644 --- a/src/test/regress/sql/indexing.sql +++ b/src/test/regress/sql/indexing.sql @@ -249,6 +249,16 @@ select relname, relkind from pg_class where relname like 'idxpart%' order by rel drop table idxpart, idxpart1, idxpart2, idxpart3; select relname, relkind from pg_class where relname like 'idxpart%' order by relname; +create table idxpart (a int, b int, c int) partition by range(a); +create index on idxpart(c); +create table idxpart1 partition of idxpart for values from (0) to (250); +create table idxpart2 partition of idxpart for values from (250) to (500); +alter table idxpart detach partition idxpart2; +\d idxpart2 +alter table idxpart2 drop column c; +\d idxpart2 +drop table idxpart, idxpart2; + -- Verify that expression indexes inherit correctly create table idxpart (a int, b int) partition by range (a); create table idxpart1 (like idxpart);