/*------------------------------------------------------------------------- * * pg_shdepend.c * routines to support manipulation of the pg_shdepend relation * * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $PostgreSQL$ * *------------------------------------------------------------------------- */ #include "postgres.h" #include "miscadmin.h" #include "access/genam.h" #include "access/heapam.h" #include "access/htup.h" #include "access/skey.h" #include "catalog/catname.h" #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/pg_shdepend.h" #include "storage/proc.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/fmgroids.h" static Oid classIdGetDbId(Oid classId); /* * Record a dependency between 2 objects via their respective ObjectAddress. * The first argument is the dependent object, the second the one it * references. * * This simply creates an entry in pg_shdepend, without any other processing. */ void recordSharedDependencyOn(const ObjectAddress *depender, const ObjectAddress *referenced) { Relation shdepRel; HeapTuple tup; char nulls[Natts_pg_shdepend]; Datum values[Natts_pg_shdepend]; int i; /* * Objects in pg_shdepend can't have SubIds. * * XXX Is this restriction actually useful, besides saving space * in pg_shdepend? */ Assert(depender->objectSubId == 0); Assert(referenced->objectSubId == 0); /* * During bootstrap, do nothing since pg_shdepend may not exist yet. * initdb will fill in appropriate pg_shdepend entries after bootstrap. */ if (IsBootstrapProcessingMode()) return; shdepRel = heap_openr(SharedDependRelationName, RowExclusiveLock); MemSet(nulls, ' ', sizeof(nulls)); /* * Form the new tuple and record the dependency. */ i = 0; values[i++] = ObjectIdGetDatum(classIdGetDbId(depender->classId)); /* dbid */ values[i++] = ObjectIdGetDatum(depender->classId); /* classid */ values[i++] = ObjectIdGetDatum(depender->objectId); /* objid */ values[i++] = ObjectIdGetDatum(referenced->classId); /* refclassid */ values[i++] = ObjectIdGetDatum(referenced->objectId); /* refobjid */ tup = heap_formtuple(shdepRel->rd_att, values, nulls); simple_heap_insert(shdepRel, tup); /* keep indexes current */ CatalogUpdateIndexes(shdepRel, tup); /* clean up */ heap_freetuple(tup); heap_close(shdepRel, RowExclusiveLock); } /* * recordDependencyOnCurrentUser * * A convenient form of recordSharedDependencyOn(). */ void recordDependencyOnCurrentUser(const ObjectAddress *object) { ObjectAddress referenced; referenced.classId = get_system_catalog_relid(ShadowRelationName); referenced.objectId = GetUserId(); referenced.objectSubId = 0; recordSharedDependencyOn(object, &referenced); } /* * shdependChangeOwner * * Update the shared dependencies to account for the new owner. */ void shdependChangeOwner(Oid classId, Oid objectId, int newOwnerSysId) { Relation sdepRel; ScanKeyData key[3]; SysScanDesc scan; HeapTuple tup; sdepRel = heap_openr(SharedDependRelationName, RowExclusiveLock); ScanKeyInit(&key[0], Anum_pg_shdepend_dbid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(classIdGetDbId(classId))); ScanKeyInit(&key[1], Anum_pg_shdepend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(classId)); ScanKeyInit(&key[2], Anum_pg_shdepend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(objectId)); scan = systable_beginscan(sdepRel, SharedDependDependerIndex, true, SnapshotNow, 3, key); while (HeapTupleIsValid(tup = systable_getnext(scan))) { Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tup); HeapTuple newtup; char repl_nulls[Natts_pg_shdepend]; char replace[Natts_pg_shdepend]; Datum *values; /* Look for tuples referring to pg_shadow */ if (sdepForm->refclassid != RelOid_pg_shadow) continue; /* * Skip a tuple we just inserted. * * XXX Another possibility would be to break out of the loop * as soon as we modify one tuple. Not sure what is best. */ if (DatumGetInt32(sdepForm->refobjid) == newOwnerSysId) continue; values = (Datum *) palloc0(sizeof(Datum) * Natts_pg_shdepend); MemSet(repl_nulls, ' ', Natts_pg_shdepend); MemSet(replace, ' ', Natts_pg_shdepend); replace[Anum_pg_shdepend_refobjid - 1] = 'r'; values[Anum_pg_shdepend_refobjid - 1] = Int32GetDatum(newOwnerSysId); newtup = heap_modifytuple(tup, sdepRel, values, repl_nulls, replace); simple_heap_update(sdepRel, &tup->t_self, newtup); /* Keep indexes current */ CatalogUpdateIndexes(sdepRel, newtup); pfree(values); heap_freetuple(newtup); } systable_endscan(scan); heap_close(sdepRel, RowExclusiveLock); } /* * shdependChangeTablespace * * Update the shared dependencies to account for the new tablespace. */ void shdependChangeTablespace(Oid classId, Oid objectId, Oid newTblspc) { Relation sdepRel; ScanKeyData key[3]; SysScanDesc scan; HeapTuple tup; int count = 0; sdepRel = heap_openr(SharedDependRelationName, RowExclusiveLock); ScanKeyInit(&key[0], Anum_pg_shdepend_dbid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(classIdGetDbId(classId))); ScanKeyInit(&key[1], Anum_pg_shdepend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(classId)); ScanKeyInit(&key[2], Anum_pg_shdepend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(objectId)); scan = systable_beginscan(sdepRel, SharedDependDependerIndex, true, SnapshotNow, 3, key); while (HeapTupleIsValid(tup = systable_getnext(scan))) { Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tup); HeapTuple newtup; char repl_nulls[Natts_pg_shdepend]; char replace[Natts_pg_shdepend]; Datum *values; /* Look for tuples referring to pg_tablespace */ if (sdepForm->refclassid != RelOid_pg_tablespace) continue; /* * Skip a tuple we just inserted; otherwise we could loop here * forever. If we do skip it, count it anyway, so we don't insert * a new entry when the user changes a relation's tablespace to * the same as before. * * XXX Another possibility would be to break out of the loop * as soon as we modify one tuple. Not sure what is best. */ if (DatumGetObjectId(sdepForm->refobjid) == newTblspc) { count++; continue; } values = (Datum *) palloc0(sizeof(Datum) * Natts_pg_shdepend); MemSet(repl_nulls, ' ', Natts_pg_shdepend); MemSet(replace, ' ', Natts_pg_shdepend); replace[Anum_pg_shdepend_refobjid - 1] = 'r'; values[Anum_pg_shdepend_refobjid - 1] = ObjectIdGetDatum(newTblspc); newtup = heap_modifytuple(tup, sdepRel, values, repl_nulls, replace); simple_heap_update(sdepRel, &tup->t_self, newtup); /* Keep indexes current */ CatalogUpdateIndexes(sdepRel, newtup); count++; pfree(values); heap_freetuple(newtup); } systable_endscan(scan); /* * If we didn't update anything, then the object didn't have a * reference registered because it was using the default tablespace. * Register one now. */ if (count == 0) { ObjectAddress self, referenced; self.classId = classId; self.objectId = objectId; self.objectSubId = 0; referenced.classId = RelOid_pg_tablespace; referenced.objectId = newTblspc; referenced.objectSubId = 0; recordSharedDependencyOn(&self, &referenced); } heap_close(sdepRel, RowExclusiveLock); } /* * A struct to keep track of dependencies found in other databases. */ typedef struct { Oid dbOid; int count; } remoteDep; /* * checkSharedDependencies * * Check whether there are shared dependency entries for a given global * object. Returns the number found. * * XXX decide what to do with those found. Display them inconditionally? */ int checkSharedDependencies(Oid classId, Oid objectId) { Relation sdepRel; ScanKeyData key[2]; SysScanDesc scan; HeapTuple tup; int count = 0; List *remDeps = NIL; ListCell *cell; ObjectAddress object; sdepRel = heap_openr(SharedDependRelationName, RowExclusiveLock); ScanKeyInit(&key[0], Anum_pg_shdepend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(classId)); ScanKeyInit(&key[1], Anum_pg_shdepend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(objectId)); scan = systable_beginscan(sdepRel, SharedDependReferenceIndex, true, SnapshotNow, 2, key); while (HeapTupleIsValid(tup = systable_getnext(scan))) { Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tup); object.classId = sdepForm->classid; object.objectId = sdepForm->objid; object.objectSubId = 0; /* * If it's a dependency local to this database, describe it. * If the dependent object is on a shared catalog, describe it. * If it's a remote dependency, keep track of it so we can * report later. * XXX this info is kept on a simple List. It might be better * to use a hash table, but I'm not sure it's worth the extra * complexity. */ if (sdepForm->dbid == MyProc->databaseId) elog(NOTICE, "in this database: %s", getObjectDescription(&object)); /* XXX optimize this. Keep a list of entries? */ else if (classIdGetDbId(sdepForm->classid) == InvalidOid) elog(NOTICE, getObjectDescription(&object)); else { remoteDep *dep; bool stored = false; foreach(cell, remDeps) { dep = lfirst(cell); if (dep->dbOid == sdepForm->dbid) { dep->count++; stored = true; break; } } if (!stored) { dep = (remoteDep *) palloc(sizeof(remoteDep)); dep->dbOid = sdepForm->dbid; dep->count = 1; remDeps = lappend(remDeps, dep); } } count++; } systable_endscan(scan); heap_close(sdepRel, RowExclusiveLock); foreach(cell, remDeps) { remoteDep *dep = lfirst(cell); object.classId = RelOid_pg_database; object.objectId = dep->dbOid; object.objectSubId = 0; elog(NOTICE, "in %s: %d objects", getObjectDescription(&object), dep->count); } list_free_deep(remDeps); return count; } /* * copyTemplateDependencies * * Routine to create the initial shared dependencies of a new database. * We simply copy the dependencies from the template database. */ void copyTemplateDependencies(Oid templateDbId, Oid newDbId) { Relation sdepRel; ScanKeyData key[1]; SysScanDesc scan; HeapTuple tup; CatalogIndexState indstate; Datum *values; char repl_nulls[Natts_pg_shdepend]; char replace[Natts_pg_shdepend]; /* I think this lock is OK? */ sdepRel = heap_openr(SharedDependRelationName, RowShareLock); ScanKeyInit(&key[0], Anum_pg_shdepend_dbid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(templateDbId)); scan = systable_beginscan(sdepRel, SharedDependDependerIndex, true, SnapshotNow, 1, key); indstate = CatalogOpenIndexes(sdepRel); values = (Datum *) palloc0(sizeof(Datum) * Natts_pg_shdepend); MemSet(repl_nulls, ' ', Natts_pg_shdepend); MemSet(replace, ' ', Natts_pg_shdepend); replace[Anum_pg_shdepend_dbid - 1] = 'r'; values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(newDbId); while (HeapTupleIsValid(tup = systable_getnext(scan))) { HeapTuple newtup; newtup = heap_modifytuple(tup, sdepRel, values, repl_nulls, replace); simple_heap_insert(sdepRel, newtup); /* Keep indexes current */ CatalogIndexInsert(indstate, newtup); heap_freetuple(newtup); } pfree(values); systable_endscan(scan); CatalogCloseIndexes(indstate); heap_close(sdepRel, RowShareLock); } /* * dropDatabaseDependencies * * Delete pg_shdepend entries corresponding to a database that's being * dropped. */ void dropDatabaseDependencies(Oid databaseId) { Relation sdepRel; ScanKeyData key[3]; SysScanDesc scan; HeapTuple tup; sdepRel = heap_openr(SharedDependRelationName, RowExclusiveLock); /* * First, delete all the entries that have the database Oid in the * dbid field. */ ScanKeyInit(&key[0], Anum_pg_shdepend_dbid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(databaseId)); scan = systable_beginscan(sdepRel, SharedDependDependerIndex, true, SnapshotNow, 1, key); while (HeapTupleIsValid(tup = systable_getnext(scan))) { simple_heap_delete(sdepRel, &tup->t_self); } systable_endscan(scan); /* Now delete all entries corresponding to the database itself */ deleteSharedDependencyRecordsFor(RelOid_pg_database, databaseId); heap_close(sdepRel, RowExclusiveLock); } /* * deleteSharedDependencyRecordsFor * * Delete pg_shdepend entries corresponding to a database-local object that's * being dropped or modified. */ void deleteSharedDependencyRecordsFor(Oid classId, Oid objectId) { Relation sdepRel; ScanKeyData key[3]; SysScanDesc scan; HeapTuple tup; sdepRel = heap_openr(SharedDependRelationName, RowExclusiveLock); ScanKeyInit(&key[0], Anum_pg_shdepend_dbid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(classIdGetDbId(classId))); ScanKeyInit(&key[1], Anum_pg_shdepend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(classId)); ScanKeyInit(&key[2], Anum_pg_shdepend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(objectId)); scan = systable_beginscan(sdepRel, SharedDependDependerIndex, true, SnapshotNow, 3, key); while (HeapTupleIsValid(tup = systable_getnext(scan))) simple_heap_delete(sdepRel, &tup->t_self); systable_endscan(scan); heap_close(sdepRel, RowExclusiveLock); } /* * deleteGlobalDependencyRecordsFor * * Delete pg_shdepend entries corresponding to a global object that's * being dropped or modified. * * XXX -- maybe this shouldn't exist at all, because when we drop a * global object we first check it isn't referenced. */ void deleteGlobalDependencyRecordsFor(Oid classId, Oid objectId) { Relation sdepRel; ScanKeyData key[2]; SysScanDesc scan; HeapTuple tup; sdepRel = heap_openr(SharedDependRelationName, RowExclusiveLock); ScanKeyInit(&key[0], Anum_pg_shdepend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(classId)); ScanKeyInit(&key[1], Anum_pg_shdepend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(objectId)); scan = systable_beginscan(sdepRel, SharedDependReferenceIndex, true, SnapshotNow, 2, key); while (HeapTupleIsValid(tup = systable_getnext(scan))) { simple_heap_delete(sdepRel, &tup->t_self); } systable_endscan(scan); heap_close(sdepRel, RowExclusiveLock); } /* * classIdGetDbId * * Get the database Id that should be used in pg_shdepend. For shared * objects, it's 0 (InvalidOid); for all other objects, it's the * current database Id. */ static Oid classIdGetDbId(Oid classId) { Relation class; Oid dbId; class = heap_open(classId, AccessShareLock); if (class->rd_rel->relisshared) dbId = InvalidOid; else dbId = MyProc->databaseId; heap_close(class, AccessShareLock); return dbId; }