/*------------------------------------------------------------------------- * * 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 "access/heapam.h" #include "catalog/catname.h" #include "catalog/index.h" #include "catalog/pg_depend.h" #include "catalog/pg_language.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/creatinh.h" #include "commands/defrem.h" #include "commands/view.h" #include "nodes/parsenodes.h" #include "parser/parse.h" /* For keyword RESTRICT */ #include "utils/fmgroids.h" #include "utils/syscache.h" static char *getObjectName(const ObjectAddress *object); static char *getObjectType(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) { 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; } /* * 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); dependDesc = heap_openr(DependRelationName, RowExclusiveLock); tupDesc = dependDesc->rd_att; if (!HeapTupleIsValid(tup = heap_formtuple(tupDesc, values, nulls))) elog(ERROR, "DependCreate: heap_formtuple failed"); heap_insert(dependDesc, tup); 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; HeapScanDesc scan; bool deprem = true; ObjectAddress objectCopy; /* * 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; /* * Test whether object being dropped is a dependee * or not. */ rel = heap_openr(DependRelationName, RowExclusiveLock); while (deprem) { ScanKeyData key[3]; ObjectAddress foundObject; char *findName; HeapTuple findTup; 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 = heap_beginscan(rel, 0, SnapshotNow, nkeys, key); /* * If no type tuple exists for the given type name, then end the scan * and return appropriate information. */ tup = heap_getnext(scan, 0); 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; heap_endscan(scan); /* * If there are dependencies and behaviour is RESTRICT * then drop them all. */ if (behavior == RESTRICT && !foundTup->alwayscascade) { elog(ERROR, "Drop Restricted as %s %s Depends on %s %s", getObjectType(&foundObject), getObjectName(&foundObject), getObjectType(&objectCopy), getObjectName(&objectCopy)); } /* * Find out what type of object this is * and call it's respective drop function. * * Assume we can find it. */ findTup = SearchSysCache(RELOID, ObjectIdGetDatum(foundObject.classId), 0, 0, 0); if (!HeapTupleIsValid(findTup)) { elog(ERROR, "dependDelete: Relation OID %d does not exist", foundObject.classId); } findName = NameStr(((Form_pg_class) GETSTRUCT(findTup))->relname); ReleaseSysCache(findTup); /* Tell the user */ if (foundTup->alwayscascade) elog(NOTICE, "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. */ if (strcmp(findName, TypeRelationName) == 0) { HeapTuple typeTup; char *typName; /* * Lets drop the type */ typeTup = SearchSysCache(TYPEOID, ObjectIdGetDatum(foundObject.objectId), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) { elog(ERROR, "dependDelete: Type %d does not exist", foundObject.objectId); } typName = NameStr(((Form_pg_type) GETSTRUCT(typeTup))->typname); ReleaseSysCache(typeTup); RemoveType(typName); /* Type behavior always RESTRICT */ } else if (strcmp(findName, RelationRelationName) == 0) { HeapTuple relTup; char relKind; char *relName; 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); ReleaseSysCache(relTup); if (relKind == 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); } else if (relKind == RELKIND_VIEW) { /* Drop VIEW */ RemoveView(relName); } /* else if (relKind == RELKIND_TOASTVALUE) */ /* Drop Toast Table */ /* else if (relKind == RELKIND_SPECIAL) */ /* Drop Special*/ else if (relKind == RELKIND_SEQUENCE || relKind == RELKIND_RELATION) { /* Drop Sequence or Table */ RemoveRelation(relName); } else elog(ERROR, "dependDelete: Unknown relkind %c", relKind); } else { elog(ERROR, "dependDelete: Unknown object class %s", findName); } /* * 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 = heap_beginscan(rel, 0, SnapshotNow, dropnkeys, dropkey); /* Drop dependencies found */ while (HeapTupleIsValid(tup = heap_getnext(scan, 0))) { simple_heap_delete(rel, &tup->t_self); } heap_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) { HeapTuple tup; char *name = "Unknown"; /* Unknown to Keep compiler quiet */ char *relName; tup = SearchSysCache(RELOID, ObjectIdGetDatum(object->classId), 0, 0, 0); /* Object Addresses should always be valid */ if (!HeapTupleIsValid(tup)) { elog(ERROR, "getObjectName: Relation %d does not exist", object->classId); } relName = NameStr(((Form_pg_class) GETSTRUCT(tup))->relname); ReleaseSysCache(tup); if (strcmp(relName, AggregateRelationName) == 0) { /* AGGREGATE */ name = "Unknown"; } else if (strcmp(relName, AttributeRelationName) == 0) { /* ATTRIBUTE */ name = "Unknown"; } else if (strcmp(relName, LanguageRelationName) == 0) { /* LANGUAGE */ HeapTuple langTup; langTup = SearchSysCache(LANGOID, ObjectIdGetDatum(object->objectId), 0, 0, 0); name = NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname); ReleaseSysCache(langTup); } else if (strcmp(relName, OperatorRelationName) == 0) { /* OPERATOR */ HeapTuple operTup; operTup = SearchSysCache(OPEROID, ObjectIdGetDatum(object->objectId), 0, 0, 0); name = NameStr(((Form_pg_operator) GETSTRUCT(operTup))->oprname); ReleaseSysCache(operTup); } else if (strcmp(relName, ProcedureRelationName) == 0) { /* FUNCTION */ HeapTuple procTup; procTup = SearchSysCache(PROCOID, ObjectIdGetDatum(object->objectId), 0, 0, 0); name = NameStr(((Form_pg_proc) GETSTRUCT(procTup))->proname); ReleaseSysCache(procTup); } else if (strcmp(relName, RelationRelationName) == 0) { /* RELATION */ HeapTuple relTup; relTup = SearchSysCache(RELOID, ObjectIdGetDatum(object->objectId), 0, 0, 0); name = NameStr(((Form_pg_class) GETSTRUCT(relTup))->relname); ReleaseSysCache(relTup); } else if (strcmp(relName, RewriteRelationName) == 0) { /* RULE */ name = "Unknown"; } else if (strcmp(relName, TypeRelationName) == 0) { /* TYPE */ HeapTuple typeTup; typeTup = SearchSysCache(TYPEOID, ObjectIdGetDatum(object->objectId), 0, 0, 0); name = NameStr(((Form_pg_type) GETSTRUCT(typeTup))->typname); ReleaseSysCache(typeTup); } else if (strcmp(relName, TriggerRelationName) == 0) { /* TRIGGER */ name = "Unknown"; } else { elog(ERROR, "getObjectName: Unknown object class %s", relName); } return name; } /* Fetch the Object Type for display */ static char * getObjectType(const ObjectAddress *object) { char *relName; char *name = "Unknown"; /* Unknown to keep compiler quiet */ HeapTuple tup; tup = SearchSysCache(RELOID, ObjectIdGetDatum(object->classId), 0, 0, 0); /* Object Addresses should always be valid */ if (!HeapTupleIsValid(tup)) { elog(ERROR, "getObjectType: Relation %d does not exist", object->classId); } relName = NameStr(((Form_pg_class) GETSTRUCT(tup))->relname); ReleaseSysCache(tup); if (strcmp(relName, AggregateRelationName) == 0) { name = "Aggregate"; } else if (strcmp(relName, AttributeRelationName) == 0) { name = "Table Attribute"; } else if (strcmp(relName, LanguageRelationName) == 0) { name = "PL Hander"; } else if (strcmp(relName, OperatorRelationName) == 0) { name = "Operator"; } else if (strcmp(relName, ProcedureRelationName) == 0) { name = "Function"; } else if (strcmp(relName, RelationRelationName) == 0) { 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); if (relKind == RELKIND_INDEX) name = "Index"; else if (relKind == RELKIND_VIEW) name = "View"; else if (relKind == RELKIND_RELATION) name = "Table"; else if (relKind == RELKIND_TOASTVALUE) name = "Toast Table"; else if (relKind == RELKIND_SEQUENCE) name = "Sequence"; else if (relKind == RELKIND_SPECIAL) name = "Special"; else elog(ERROR, "dependDelete: Unknown relkind %c", relKind); /* Should this be broken down to INDEX, View, Table, etc? */ } else if (strcmp(relName, RewriteRelationName) == 0) { name = "Rule"; } else if (strcmp(relName, TypeRelationName) == 0) { name = "Type"; } else if (strcmp(relName, TriggerRelationName) == 0) { name = "Trigger"; } else { elog(ERROR, "getObjectType: Unknown object class %s", relName); } return name; }