/*------------------------------------------------------------------------- * * depend.c * random postgres portal and utility support code * * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $Header$ * * NOTES * Manage dependencies between varying system objects. * *------------------------------------------------------------------------- */ #include "postgres.h" #include "miscadmin.h" #include "access/heapam.h" #include "access/genam.h" #include "catalog/catname.h" #include "catalog/index.h" #include "catalog/indexing.h" #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" #include "catalog/pg_language.h" #include "catalog/pg_namespace.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/comment.h" #include "commands/defrem.h" #include "commands/tablecmds.h" #include "commands/trigger.h" #include "commands/view.h" #include "nodes/parsenodes.h" #include "nodes/pg_list.h" #include "nodes/makefuncs.h" #include "rewrite/rewriteRemove.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/syscache.h" static char *getObjectName(const ObjectAddress *object); static char *getObjectType(const ObjectAddress *object); static bool isObjectPinned(const ObjectAddress *object, Relation rel); static bool isStructureOfPin(const ObjectAddress *object); /* * Records a requested dependency between 2 objects via their * respective objectAddress. * * It makes the assumption that both objects currently (or will) * exist before the end of the transaction. * * Behaviour, if true tells dependDelete to ignore RESTRICT as * the issued behaviour at the time and cascade to the object * anyway. The reason for this is sequences generated by SERIAL * and array types. */ void dependCreate(const ObjectAddress *depender, const ObjectAddress *dependee, bool behavior) { if (!IsBootstrapProcessingMode()) { Relation dependDesc; TupleDesc tupDesc; HeapTuple tup; int i; char nulls[Natts_pg_depend]; Datum values[Natts_pg_depend]; for (i = 0; i < Natts_pg_depend; ++i) { nulls[i] = ' '; values[i] = (Datum) 0; } dependDesc = heap_openr(DependRelationName, RowExclusiveLock); /* Test to see if the object is pinned (permenant) */ if (!isObjectPinned(dependee, dependDesc)) { Relation idescs[Num_pg_depend_indices]; /* * Record the Dependency. Assume it can be added, and * doesn't previously exist. Some items (type creation) * may add duplicates. */ values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId); values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId); values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId); values[Anum_pg_depend_depclassid - 1] = ObjectIdGetDatum(dependee->classId); values[Anum_pg_depend_depobjid - 1] = ObjectIdGetDatum(dependee->objectId); values[Anum_pg_depend_depobjsubid - 1] = Int32GetDatum(dependee->objectSubId); values[Anum_pg_depend_alwayscascade -1] = BoolGetDatum(behavior); tupDesc = dependDesc->rd_att; if (!HeapTupleIsValid(tup = heap_formtuple(tupDesc, values, nulls))) elog(ERROR, "DependCreate: heap_formtuple failed"); simple_heap_insert(dependDesc, tup); /* * Keep indices current */ CatalogOpenIndices(Num_pg_depend_indices, Name_pg_depend_indices, idescs); CatalogIndexInsert(idescs, Num_pg_depend_indices, dependDesc, tup); CatalogCloseIndices(Num_pg_depend_indices, idescs); } heap_close(dependDesc, RowExclusiveLock); /* Required? */ } } /* * Drops the interdependencies between the object and it's * children depending on the behavior specified. RESTRICT or * CASCADE types supported. * * RESTRICT will abort the transaction if other objects depend * on this one. * * CASCADE will drop all objects which depend on the supplied * object address. */ void dependDelete(ObjectAddress *object, int behavior) { Relation rel; ScanKeyData dropkey[3]; HeapTuple tup; int dropnkeys = 0; SysScanDesc scan; bool deprem = true; ObjectAddress objectCopy; Assert(behavior == DEPEND_RESTRICT || behavior == DEPEND_CASCADE || behavior == DEPEND_IMPLICITONLY); /* * A copy of the object passed in needs to be taken, else we risk * it being wiped from memory mid way through the drops. */ objectCopy.classId = object->classId; objectCopy.objectId = object->objectId; objectCopy.objectSubId = object->objectSubId; /* Delete any comments associated with this object */ DeleteComments(objectCopy.objectId, objectCopy.classId, objectCopy.objectSubId); /* * Test whether object being dropped is a dependee * or not. */ rel = heap_openr(DependRelationName, RowExclusiveLock); /* If object is pinned dissallow it's removal */ if (isObjectPinned(&objectCopy, rel)) elog(ERROR, "Drop Restricted as %s %s is an essential for the database to function", getObjectType(&objectCopy), getObjectName(&objectCopy)); while (deprem) { ScanKeyData key[3]; ObjectAddress foundObject; Form_pg_depend foundTup; int nkeys = 0; /* Class Oid */ Assert(objectCopy.classId != InvalidOid); ScanKeyEntryInitialize(&key[nkeys++], 0, Anum_pg_depend_depclassid, F_OIDEQ, ObjectIdGetDatum(objectCopy.classId)); /* Object Oid */ Assert(objectCopy.objectId != InvalidOid); ScanKeyEntryInitialize(&key[nkeys++], 0, Anum_pg_depend_depobjid, F_OIDEQ, ObjectIdGetDatum(objectCopy.objectId)); /* SubObject Id */ ScanKeyEntryInitialize(&key[nkeys++], 0, Anum_pg_depend_depobjsubid, F_INT4EQ, Int32GetDatum(objectCopy.objectSubId)); scan = systable_beginscan(rel, DependDependeeIndex, true, SnapshotNow, nkeys, key); /* * If no type tuple exists for the given type name, then end the scan * and return appropriate information. */ tup = systable_getnext(scan); if (!HeapTupleIsValid(tup)) { deprem = false; continue; } foundTup = (Form_pg_depend) GETSTRUCT(tup); /* * Lets load up and test the object which depends * on the one we want to drop. */ foundObject.classId = foundTup->classid; foundObject.objectId = foundTup->objid; foundObject.objectSubId = foundTup->objsubid; systable_endscan(scan); /* * If there are dependencies and behaviour is RESTRICT * then drop them all. */ if (behavior == DEPEND_RESTRICT && !foundTup->alwayscascade) elog(ERROR, "Drop Restricted as %s %s Depends on %s %s", getObjectType(&foundObject), getObjectName(&foundObject), getObjectType(&objectCopy), getObjectName(&objectCopy)); /* * When IMPLICITONLY we don't want to cascade or restrict. * Simply drop all items implicitly associated with this object. */ if (behavior == DEPEND_IMPLICITONLY && !foundTup->alwayscascade) { continue; } /* Tell the user */ if (foundTup->alwayscascade) elog(DEBUG1, "Implicit drop of %s %s", getObjectType(&foundObject), getObjectName(&foundObject)); else elog(NOTICE, "Cascading drop to %s %s", getObjectType(&foundObject), getObjectName(&foundObject)); /* * The below functions are expected to cascade back here by calling * dependDelete(). If they don't, a partial cascade can occur leaving * poor relations in place. */ switch (foundObject.classId) { case RelOid_pg_proc: RemoveFunctionById(foundObject.objectId, behavior); break; case RelOid_pg_class: { HeapTuple relTup; char relKind; char *relName; char *schemaName; relTup = SearchSysCache(RELOID, ObjectIdGetDatum(foundObject.objectId), 0, 0, 0); if (!HeapTupleIsValid(relTup)) { elog(ERROR, "dependDelete: Relation %d does not exist", foundObject.objectId); } relKind = ((Form_pg_class) GETSTRUCT(relTup))->relkind; relName = NameStr(((Form_pg_class) GETSTRUCT(relTup))->relname); schemaName = get_namespace_name(((Form_pg_class) GETSTRUCT(relTup))->relnamespace); ReleaseSysCache(relTup); switch(relKind) { case RELKIND_INDEX: /* Drop INDEX * * Future use will use below once indexes drops are * corrected to be selfcontained. Messages of tuple * updates occur if more than a single index is removed * during a table drop. */ index_drop(foundObject.objectId, behavior); break; case RELKIND_VIEW: RemoveView(makeRangeVar(schemaName, relName), behavior); break; case RELKIND_RELATION: case RELKIND_SEQUENCE: case RELKIND_TOASTVALUE: case RELKIND_SPECIAL: RemoveRelation(makeRangeVar(schemaName, relName), behavior); break; default: elog(ERROR, "dependDelete: Unknown relkind %c", relKind); } break; } case RelOid_pg_type: { TypeName *typename; /* Make a TypeName so we can use standard type lookup machinery */ typename = makeNode(TypeName); typename->names = NIL; typename->typeid = foundObject.objectId; typename->typmod = -1; typename->arrayBounds = NIL; /* Drop the type */ RemoveTypeByTypeName(typename, behavior); break; } case RelOid_pg_attribute: elog(ERROR, "Removing Attribute"); break; default: /* Can't compare to a 'static' OID */ if (foundObject.classId == get_relname_relid(AggregateRelationName, PG_CATALOG_NAMESPACE)) elog(ERROR, "Removing Aggregate"); else if (foundObject.classId == get_relname_relid(ConstraintRelationName, PG_CATALOG_NAMESPACE)) DropConstraintById(foundObject.objectId, behavior); else if (foundObject.classId == get_relname_relid(LanguageRelationName, PG_CATALOG_NAMESPACE)) elog(ERROR, "PL Handler"); else if (foundObject.classId == get_relname_relid(OperatorRelationName, PG_CATALOG_NAMESPACE)) RemoveOperatorById(foundObject.objectId, behavior); else if (foundObject.classId == get_relname_relid(RewriteRelationName, PG_CATALOG_NAMESPACE)) RemoveRewriteRuleById(foundObject.objectId, behavior); else if (foundObject.classId == get_relname_relid(TriggerRelationName, PG_CATALOG_NAMESPACE)) DropTriggerById(foundObject.objectId, behavior); else elog(ERROR, "getObjectType: Unknown object class %d", foundObject.classId); } /* * We need to assume that cascaded items could potentially * remove dependencies we want to as well. The simplest * way to ovoid double deletions (and warnings about tuples * being modified twice) is to rescan our list after * bumping the command counter. */ CommandCounterIncrement(); } /* * Now go through the whole thing again looking for our object * as the depender so we can drop those dependencies. */ /* Class Oid */ Assert(objectCopy.classId != InvalidOid); ScanKeyEntryInitialize(&dropkey[dropnkeys++], 0, Anum_pg_depend_classid, F_OIDEQ, ObjectIdGetDatum(objectCopy.classId)); /* Object Oid */ Assert(objectCopy.objectId != InvalidOid); ScanKeyEntryInitialize(&dropkey[dropnkeys++], 0, Anum_pg_depend_objid, F_OIDEQ, ObjectIdGetDatum(objectCopy.objectId)); /* SubObject Id */ ScanKeyEntryInitialize(&dropkey[dropnkeys++], 0, Anum_pg_depend_objsubid, F_INT4EQ, Int32GetDatum(objectCopy.objectSubId)); scan = systable_beginscan(rel, DependDependerIndex, true, SnapshotNow, dropnkeys, dropkey); /* Drop dependencies found */ while (HeapTupleIsValid(tup = systable_getnext(scan))) { simple_heap_delete(rel, &tup->t_self); } systable_endscan(scan); /* Cleanup and get out */ heap_close(rel, RowExclusiveLock); } /* Delete function to save time */ void dependDeleteTuple(const HeapTuple tup, const Relation relation, int behavior) { ObjectAddress myself; /* Collect the information and call the real delete function */ myself.classId = RelationGetRelid(relation); myself.objectId = tup->t_data->t_oid; myself.objectSubId = 0; dependDelete(&myself, behavior); } /* Fetch the Object Name for display */ static char * getObjectName(const ObjectAddress *object) { char *name = "Unknown"; /* Unknown to Keep compiler quiet */ switch (object->classId) { case RelOid_pg_proc: { /* FUNCTION */ HeapTuple procTup; procTup = SearchSysCache(PROCOID, ObjectIdGetDatum(object->objectId), 0, 0, 0); name = NameStr(((Form_pg_proc) GETSTRUCT(procTup))->proname); ReleaseSysCache(procTup); break; } case RelOid_pg_class: { /* RELATION */ HeapTuple relTup; relTup = SearchSysCache(RELOID, ObjectIdGetDatum(object->objectId), 0, 0, 0); name = NameStr(((Form_pg_class) GETSTRUCT(relTup))->relname); ReleaseSysCache(relTup); break; } case RelOid_pg_type: { /* TYPE */ HeapTuple typeTup; typeTup = SearchSysCache(TYPEOID, ObjectIdGetDatum(object->objectId), 0, 0, 0); name = NameStr(((Form_pg_type) GETSTRUCT(typeTup))->typname); ReleaseSysCache(typeTup); break; } case RelOid_pg_attribute: /* ATTRIBUTE */ name = "Unknown"; break; default: /* Can't compare to a 'static' OID */ if (object->classId == get_relname_relid(AggregateRelationName, PG_CATALOG_NAMESPACE)) /* AGGREGATE */ name = "Unknown"; else if (object->classId == get_relname_relid(ConstraintRelationName, PG_CATALOG_NAMESPACE)) { HeapTuple tup; Relation conDesc; ScanKeyData skey[1]; SysScanDesc rcscan; int i = 0; /* CONSTRAINT */ ScanKeyEntryInitialize(&skey[i++], 0, ObjectIdAttributeNumber, F_OIDEQ, ObjectIdGetDatum(object->objectId)); conDesc = heap_openr(ConstraintRelationName, RowExclusiveLock); rcscan = systable_beginscan(conDesc, ConstraintOidIndex, true, SnapshotNow, i, skey); tup = systable_getnext(rcscan); if (!HeapTupleIsValid(tup)) /* * elog(ERROR, "Constraint OID %d missing", object->objectId); * * Due to circular dependencies we simply say we don't know rather * than the above line. This shouldn't happen in any other case * than a the circular constraint dependency anyway. */ name = ""; else name = NameStr(((Form_pg_constraint) GETSTRUCT(tup))->conname); /* Clean up */ systable_endscan(rcscan); } else if (object->classId == get_relname_relid(LanguageRelationName, PG_CATALOG_NAMESPACE)) { /* LANGUAGE */ HeapTuple langTup; langTup = SearchSysCache(LANGOID, ObjectIdGetDatum(object->objectId), 0, 0, 0); name = NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname); ReleaseSysCache(langTup); } else if (object->classId == get_relname_relid(OperatorRelationName, PG_CATALOG_NAMESPACE)) { /* OPERATOR */ HeapTuple operTup; operTup = SearchSysCache(OPEROID, ObjectIdGetDatum(object->objectId), 0, 0, 0); name = NameStr(((Form_pg_operator) GETSTRUCT(operTup))->oprname); ReleaseSysCache(operTup); } else if (object->classId == get_relname_relid(RewriteRelationName, PG_CATALOG_NAMESPACE)) /* RULE */ name = "Unknown"; else if (object->classId == get_relname_relid(TriggerRelationName, PG_CATALOG_NAMESPACE)) /* TRIGGER */ name = "Unknown"; else elog(ERROR, "getObjectType: Unknown object class %d", object->classId); } return name; } /* Fetch the Object Type for display */ static char * getObjectType(const ObjectAddress *object) { char *name = "Unknown"; /* Unknown to keep compiler quiet */ switch (object->classId) { case RelOid_pg_proc: name = "Function"; break; case RelOid_pg_class: { HeapTuple relTup; char relKind; relTup = SearchSysCache(RELOID, ObjectIdGetDatum(object->objectId), 0, 0, 0); if (!HeapTupleIsValid(relTup)) { elog(ERROR, "getObjectType: Relation %d does not exist", object->objectId); } relKind = ((Form_pg_class) GETSTRUCT(relTup))->relkind; ReleaseSysCache(relTup); switch(relKind) { case RELKIND_INDEX: name = "Index"; break; case RELKIND_VIEW: name = "View"; break; case RELKIND_RELATION: name = "Table"; break; case RELKIND_TOASTVALUE: name = "Toast Table"; break; case RELKIND_SEQUENCE: name = "Sequence"; break; case RELKIND_SPECIAL: name = "Special"; break; default: elog(ERROR, "dependDelete: Unknown relkind %c", relKind); } break; } case RelOid_pg_type: name = "Type"; break; case RelOid_pg_attribute: name = "Table Attribute"; break; default: /* Can't compare to a 'static' OID */ if (object->classId == get_relname_relid(AggregateRelationName, PG_CATALOG_NAMESPACE)) name = "Aggregate"; else if (object->classId == get_relname_relid(ConstraintRelationName, PG_CATALOG_NAMESPACE)) name = "Constraint"; else if (object->classId == get_relname_relid(LanguageRelationName, PG_CATALOG_NAMESPACE)) name = "PL Hander"; else if (object->classId == get_relname_relid(OperatorRelationName, PG_CATALOG_NAMESPACE)) name = "Operator"; else if (object->classId == get_relname_relid(RewriteRelationName, PG_CATALOG_NAMESPACE)) name = "Rule"; else if (object->classId == get_relname_relid(TriggerRelationName, PG_CATALOG_NAMESPACE)) name = "Trigger"; else elog(ERROR, "getObjectType: Unknown object class %d", object->classId); } return name; } /* * isPinnedDependee() * * Test if the dependee of a found object is a permenant requirement * for basic database functionality. */ static bool isStructureOfPin(const ObjectAddress *object) { bool ret = false; if (object->classId == InvalidOid && object->objectId == InvalidOid && object->objectSubId == 0) ret = true; return(ret); } /* * isObjectPinned() * * Test if an object is permenantly required for basic database functionality */ static bool isObjectPinned(const ObjectAddress *object, Relation rel) { SysScanDesc scan; HeapTuple tup; bool ret = false; ScanKeyData key[6]; int nkeys = 0; /* Pinned in Depender Slot*/ Assert(object->classId); ScanKeyEntryInitialize(&key[nkeys++], 0, Anum_pg_depend_classid, F_OIDEQ, ObjectIdGetDatum(object->classId)); Assert(object->objectId); ScanKeyEntryInitialize(&key[nkeys++], 0, Anum_pg_depend_objid, F_OIDEQ, ObjectIdGetDatum(object->objectId)); ScanKeyEntryInitialize(&key[nkeys++], 0, Anum_pg_depend_objsubid, F_INT4EQ, Int32GetDatum(object->objectSubId)); scan = systable_beginscan(rel, DependDependerIndex, true, SnapshotNow, nkeys, key); /* * If we find a match, skip the entire process. */ tup = systable_getnext(scan); if (HeapTupleIsValid(tup)) { ObjectAddress foundObject; Form_pg_depend foundTup; /* * Pinned objects have a dependee ObjectAddress of 0, 0, 0 * and will only ever have one entry. */ foundTup = (Form_pg_depend) GETSTRUCT(tup); foundObject.classId = foundTup->depclassid; foundObject.objectId = foundTup->depobjid; foundObject.objectSubId = foundTup->depobjsubid; if (isStructureOfPin(&foundObject)) ret = true; } /* Cleanup and return */ systable_endscan(scan); return(ret); }