* pg_shdepend.c
* routines to support manipulation of the pg_shdepend relation
*
- * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/pg_shdepend.c,v 1.9 2006/05/04 16:07:29 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/pg_shdepend.c,v 1.33 2009/06/04 18:33:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "access/genam.h"
#include "access/heapam.h"
-#include "utils/acl.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_type.h"
#include "commands/conversioncmds.h"
#include "commands/defrem.h"
+#include "commands/proclang.h"
#include "commands/schemacmds.h"
#include "commands/tablecmds.h"
#include "commands/typecmds.h"
-#include "lib/stringinfo.h"
+#include "storage/lmgr.h"
#include "miscadmin.h"
+#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/syscache.h"
+#include "utils/tqual.h"
typedef enum
Oid **diff);
static Oid classIdGetDbId(Oid classId);
static void shdepLockAndCheckObject(Oid classId, Oid objectId);
-static void shdepChangeDep(Relation sdepRel, Oid classid, Oid objid,
- Oid refclassid, Oid refobjid,
- SharedDependencyType deptype);
-static void shdepAddDependency(Relation sdepRel, Oid classId, Oid objectId,
- Oid refclassId, Oid refobjId,
- SharedDependencyType deptype);
-static void shdepDropDependency(Relation sdepRel, Oid classId, Oid objectId,
- Oid refclassId, Oid refobjId,
- SharedDependencyType deptype);
+static void shdepChangeDep(Relation sdepRel,
+ Oid classid, Oid objid, int32 objsubid,
+ Oid refclassid, Oid refobjid,
+ SharedDependencyType deptype);
+static void shdepAddDependency(Relation sdepRel,
+ Oid classId, Oid objectId, int32 objsubId,
+ Oid refclassId, Oid refobjId,
+ SharedDependencyType deptype);
+static void shdepDropDependency(Relation sdepRel,
+ Oid classId, Oid objectId, int32 objsubId,
+ bool drop_subobjects,
+ Oid refclassId, Oid refobjId,
+ SharedDependencyType deptype);
static void storeObjectDescription(StringInfo descs, objectType type,
ObjectAddress *object,
SharedDependencyType deptype,
sdepRel))
{
shdepAddDependency(sdepRel, depender->classId, depender->objectId,
+ depender->objectSubId,
referenced->classId, referenced->objectId,
deptype);
}
* locked.
*/
static void
-shdepChangeDep(Relation sdepRel, Oid classid, Oid objid,
+shdepChangeDep(Relation sdepRel,
+ Oid classid, Oid objid, int32 objsubid,
Oid refclassid, Oid refobjid,
SharedDependencyType deptype)
{
Oid dbid = classIdGetDbId(classid);
HeapTuple oldtup = NULL;
HeapTuple scantup;
- ScanKeyData key[3];
+ ScanKeyData key[4];
SysScanDesc scan;
/*
Anum_pg_shdepend_objid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(objid));
+ ScanKeyInit(&key[3],
+ Anum_pg_shdepend_objsubid,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(objsubid));
scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
- SnapshotNow, 3, key);
+ SnapshotNow, 4, key);
while ((scantup = systable_getnext(scan)) != NULL)
{
/* Caller screwed up if multiple matches */
if (oldtup)
elog(ERROR,
- "multiple pg_shdepend entries for object %u/%u deptype %c",
- classid, objid, deptype);
+ "multiple pg_shdepend entries for object %u/%u/%d deptype %c",
+ classid, objid, objsubid, deptype);
oldtup = heap_copytuple(scantup);
}
Datum values[Natts_pg_shdepend];
bool nulls[Natts_pg_shdepend];
- memset(nulls, 0, sizeof(nulls));
+ memset(nulls, false, sizeof(nulls));
values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(dbid);
values[Anum_pg_shdepend_classid - 1] = ObjectIdGetDatum(classid);
values[Anum_pg_shdepend_objid - 1] = ObjectIdGetDatum(objid);
+ values[Anum_pg_shdepend_objsubid - 1] = Int32GetDatum(objsubid);
values[Anum_pg_shdepend_refclassid - 1] = ObjectIdGetDatum(refclassid);
values[Anum_pg_shdepend_refobjid - 1] = ObjectIdGetDatum(refobjid);
* changeDependencyOnOwner
*
* Update the shared dependencies to account for the new owner.
+ *
+ * Note: we don't need an objsubid argument because only whole objects
+ * have owners.
*/
void
changeDependencyOnOwner(Oid classId, Oid objectId, Oid newOwnerId)
sdepRel = heap_open(SharedDependRelationId, RowExclusiveLock);
/* Adjust the SHARED_DEPENDENCY_OWNER entry */
- shdepChangeDep(sdepRel, classId, objectId,
+ shdepChangeDep(sdepRel,
+ classId, objectId, 0,
AuthIdRelationId, newOwnerId,
SHARED_DEPENDENCY_OWNER);
* to make the various ALTER OWNER routines each know about it.
*----------
*/
- shdepDropDependency(sdepRel, classId, objectId,
+ shdepDropDependency(sdepRel, classId, objectId, 0, true,
AuthIdRelationId, newOwnerId,
SHARED_DEPENDENCY_ACL);
* updateAclDependencies
* Update the pg_shdepend info for an object's ACL during GRANT/REVOKE.
*
- * classId, objectId: identify the object whose ACL this is
+ * classId, objectId, objsubId: identify the object whose ACL this is
* ownerId: role owning the object
* isGrant: are we adding or removing ACL entries?
* noldmembers, oldmembers: array of roleids appearing in old ACL
* before return.
*/
void
-updateAclDependencies(Oid classId, Oid objectId, Oid ownerId, bool isGrant,
+updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,
+ Oid ownerId, bool isGrant,
int noldmembers, Oid *oldmembers,
int nnewmembers, Oid *newmembers)
{
continue;
if (isGrant)
- shdepAddDependency(sdepRel, classId, objectId,
+ shdepAddDependency(sdepRel, classId, objectId, objsubId,
AuthIdRelationId, roleid,
SHARED_DEPENDENCY_ACL);
else
- shdepDropDependency(sdepRel, classId, objectId,
+ shdepDropDependency(sdepRel, classId, objectId, objsubId,
+ false, /* exact match on objsubId */
AuthIdRelationId, roleid,
SHARED_DEPENDENCY_ACL);
}
* checkSharedDependencies
*
* Check whether there are shared dependency entries for a given shared
- * object. Returns a string containing a newline-separated list of object
+ * object; return true if so.
+ *
+ * In addition, return a string containing a newline-separated list of object
* descriptions that depend on the shared object, or NULL if none is found.
+ * We actually return two such strings; the "detail" result is suitable for
+ * returning to the client as an errdetail() string, and is limited in size.
+ * The "detail_log" string is potentially much longer, and should be emitted
+ * to the server log only.
*
* We can find three different kinds of dependencies: dependencies on objects
* of the current database; dependencies on shared objects; and dependencies
*
* If we find a SHARED_DEPENDENCY_PIN entry, we can error out early.
*/
-char *
-checkSharedDependencies(Oid classId, Oid objectId)
+bool
+checkSharedDependencies(Oid classId, Oid objectId,
+ char **detail_msg, char **detail_log_msg)
{
Relation sdepRel;
ScanKeyData key[2];
SysScanDesc scan;
HeapTuple tup;
- int totalDeps = 0;
- int numLocalDeps = 0;
- int numSharedDeps = 0;
+ int numReportedDeps = 0;
+ int numNotReportedDeps = 0;
+ int numNotReportedDbs = 0;
List *remDeps = NIL;
ListCell *cell;
ObjectAddress object;
StringInfoData descs;
+ StringInfoData alldescs;
/*
- * We try to limit the number of reported dependencies to something sane,
- * both for the user's sake and to avoid blowing out memory.
+ * We limit the number of dependencies reported to the client to
+ * MAX_REPORTED_DEPS, since client software may not deal well with
+ * enormous error strings. The server log always gets a full report.
*/
#define MAX_REPORTED_DEPS 100
initStringInfo(&descs);
+ initStringInfo(&alldescs);
sdepRel = heap_open(SharedDependRelationId, AccessShareLock);
object.classId = sdepForm->classid;
object.objectId = sdepForm->objid;
- object.objectSubId = 0;
+ object.objectSubId = sdepForm->objsubid;
/*
* If it's a dependency local to this database or it's a shared
*/
if (sdepForm->dbid == MyDatabaseId)
{
- numLocalDeps++;
- if (++totalDeps <= MAX_REPORTED_DEPS)
+ if (numReportedDeps < MAX_REPORTED_DEPS)
+ {
+ numReportedDeps++;
storeObjectDescription(&descs, LOCAL_OBJECT, &object,
sdepForm->deptype, 0);
+ }
+ else
+ numNotReportedDeps++;
+ storeObjectDescription(&alldescs, LOCAL_OBJECT, &object,
+ sdepForm->deptype, 0);
}
else if (sdepForm->dbid == InvalidOid)
{
- numSharedDeps++;
- if (++totalDeps <= MAX_REPORTED_DEPS)
+ if (numReportedDeps < MAX_REPORTED_DEPS)
+ {
+ numReportedDeps++;
storeObjectDescription(&descs, SHARED_OBJECT, &object,
sdepForm->deptype, 0);
+ }
+ else
+ numNotReportedDeps++;
+ storeObjectDescription(&alldescs, SHARED_OBJECT, &object,
+ sdepForm->deptype, 0);
}
else
{
dep->dbOid = sdepForm->dbid;
dep->count = 1;
remDeps = lappend(remDeps, dep);
- totalDeps++;
}
}
}
heap_close(sdepRel, AccessShareLock);
- if (totalDeps > MAX_REPORTED_DEPS)
- {
- /*
- * Report seems unreasonably long, so reduce it to per-database info
- *
- * Note: we don't ever suppress per-database totals, which should be
- * OK as long as there aren't too many databases ...
- */
- descs.len = 0; /* reset to empty */
- descs.data[0] = '\0';
-
- if (numLocalDeps > 0)
- {
- appendStringInfo(&descs, _("%d objects in this database"),
- numLocalDeps);
- if (numSharedDeps > 0)
- appendStringInfoChar(&descs, '\n');
- }
- if (numSharedDeps > 0)
- appendStringInfo(&descs, _("%d shared objects"),
- numSharedDeps);
- }
-
+ /*
+ * Summarize dependencies in remote databases.
+ */
foreach(cell, remDeps)
{
remoteDep *dep = lfirst(cell);
object.objectId = dep->dbOid;
object.objectSubId = 0;
- storeObjectDescription(&descs, REMOTE_OBJECT, &object,
+ if (numReportedDeps < MAX_REPORTED_DEPS)
+ {
+ numReportedDeps++;
+ storeObjectDescription(&descs, REMOTE_OBJECT, &object,
+ SHARED_DEPENDENCY_INVALID, dep->count);
+ }
+ else
+ numNotReportedDbs++;
+ storeObjectDescription(&alldescs, REMOTE_OBJECT, &object,
SHARED_DEPENDENCY_INVALID, dep->count);
}
if (descs.len == 0)
{
pfree(descs.data);
- return NULL;
+ pfree(alldescs.data);
+ *detail_msg = *detail_log_msg = NULL;
+ return false;
}
- return descs.data;
+ if (numNotReportedDeps > 0)
+ appendStringInfo(&descs, ngettext("\nand %d other object "
+ "(see server log for list)",
+ "\nand %d other objects "
+ "(see server log for list)",
+ numNotReportedDeps),
+ numNotReportedDeps);
+ if (numNotReportedDbs > 0)
+ appendStringInfo(&descs, ngettext("\nand objects in %d other database "
+ "(see server log for list)",
+ "\nand objects in %d other databases "
+ "(see server log for list)",
+ numNotReportedDbs),
+ numNotReportedDbs);
+
+ *detail_msg = descs.data;
+ *detail_log_msg = alldescs.data;
+ return true;
}
/*
systable_endscan(scan);
/* Now delete all entries corresponding to the database itself */
- shdepDropDependency(sdepRel, DatabaseRelationId, databaseId,
+ shdepDropDependency(sdepRel, DatabaseRelationId, databaseId, 0, true,
InvalidOid, InvalidOid,
SHARED_DEPENDENCY_INVALID);
* Delete all pg_shdepend entries corresponding to an object that's being
* dropped or modified. The object is assumed to be either a shared object
* or local to the current database (the classId tells us which).
+ *
+ * If objectSubId is zero, we are deleting a whole object, so get rid of
+ * pg_shdepend entries for subobjects as well.
*/
void
-deleteSharedDependencyRecordsFor(Oid classId, Oid objectId)
+deleteSharedDependencyRecordsFor(Oid classId, Oid objectId, int32 objectSubId)
{
Relation sdepRel;
sdepRel = heap_open(SharedDependRelationId, RowExclusiveLock);
- shdepDropDependency(sdepRel, classId, objectId,
+ shdepDropDependency(sdepRel, classId, objectId, objectSubId,
+ (objectSubId == 0),
InvalidOid, InvalidOid,
SHARED_DEPENDENCY_INVALID);
* locked.
*/
static void
-shdepAddDependency(Relation sdepRel, Oid classId, Oid objectId,
+shdepAddDependency(Relation sdepRel,
+ Oid classId, Oid objectId, int32 objsubId,
Oid refclassId, Oid refobjId,
SharedDependencyType deptype)
{
values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(classIdGetDbId(classId));
values[Anum_pg_shdepend_classid - 1] = ObjectIdGetDatum(classId);
values[Anum_pg_shdepend_objid - 1] = ObjectIdGetDatum(objectId);
+ values[Anum_pg_shdepend_objsubid - 1] = Int32GetDatum(objsubId);
values[Anum_pg_shdepend_refclassid - 1] = ObjectIdGetDatum(refclassId);
values[Anum_pg_shdepend_refobjid - 1] = ObjectIdGetDatum(refobjId);
* Internal workhorse for deleting entries from pg_shdepend.
*
* We drop entries having the following properties:
- * dependent object is the one identified by classId/objectId
+ * dependent object is the one identified by classId/objectId/objsubId
* if refclassId isn't InvalidOid, it must match the entry's refclassid
* if refobjId isn't InvalidOid, it must match the entry's refobjid
* if deptype isn't SHARED_DEPENDENCY_INVALID, it must match entry's deptype
*
+ * If drop_subobjects is true, we ignore objsubId and consider all entries
+ * matching classId/objectId.
+ *
* sdepRel must be the pg_shdepend relation, already opened and suitably
* locked.
*/
static void
-shdepDropDependency(Relation sdepRel, Oid classId, Oid objectId,
+shdepDropDependency(Relation sdepRel,
+ Oid classId, Oid objectId, int32 objsubId,
+ bool drop_subobjects,
Oid refclassId, Oid refobjId,
SharedDependencyType deptype)
{
- ScanKeyData key[3];
+ ScanKeyData key[4];
+ int nkeys;
SysScanDesc scan;
HeapTuple tup;
Anum_pg_shdepend_objid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(objectId));
+ if (drop_subobjects)
+ nkeys = 3;
+ else
+ {
+ ScanKeyInit(&key[3],
+ Anum_pg_shdepend_objsubid,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(objsubId));
+ nkeys = 4;
+ }
scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
- SnapshotNow, 3, key);
+ SnapshotNow, nkeys, key);
while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
* Get the database Id that should be used in pg_shdepend, given the OID
* of the catalog containing the object. For shared objects, it's 0
* (InvalidOid); for all other objects, it's the current database Id.
- *
- * XXX it's awfully tempting to hard-wire this instead of doing a syscache
- * lookup ... but resist the temptation, unless you can prove it's a
- * bottleneck.
*/
static Oid
classIdGetDbId(Oid classId)
{
Oid dbId;
- HeapTuple tup;
-
- tup = SearchSysCache(RELOID,
- ObjectIdGetDatum(classId),
- 0, 0, 0);
- if (!HeapTupleIsValid(tup))
- elog(ERROR, "cache lookup failed for relation %u", classId);
- if (((Form_pg_class) GETSTRUCT(tup))->relisshared)
+ if (IsSharedRelation(classId))
dbId = InvalidOid;
else
dbId = MyDatabaseId;
- ReleaseSysCache(tup);
-
return dbId;
}
case REMOTE_OBJECT:
/* translator: %s will always be "database %s" */
- appendStringInfo(descs, _("%d objects in %s"), count, objdesc);
+ appendStringInfo(descs, ngettext("%d object in %s",
+ "%d objects in %s",
+ count),
+ count, objdesc);
break;
default:
*
* Drop the objects owned by any one of the given RoleIds. If a role has
* access to an object, the grant will be removed as well (but the object
- * will not, of course.)
+ * will not, of course).
+ *
+ * We can revoke grants immediately while doing the scan, but drops are
+ * saved up and done all at once with performMultipleDeletions. This
+ * is necessary so that we don't get failures from trying to delete
+ * interdependent objects in the wrong order.
*/
void
shdepDropOwned(List *roleids, DropBehavior behavior)
{
Relation sdepRel;
ListCell *cell;
+ ObjectAddresses *deleteobjs;
- sdepRel = heap_open(SharedDependRelationId, AccessExclusiveLock);
+ deleteobjs = new_object_addresses();
+
+ /*
+ * We don't need this strong a lock here, but we'll call routines that
+ * acquire RowExclusiveLock. Better get that right now to avoid potential
+ * deadlock failures.
+ */
+ sdepRel = heap_open(SharedDependRelationId, RowExclusiveLock);
/*
* For each role, find the dependent objects and drop them using the
while ((tuple = systable_getnext(scan)) != NULL)
{
Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tuple);
+ InternalGrant istmt;
+ ObjectAddress obj;
- /* We only operate on objects on the current database */
+ /* We only operate on objects in the current database */
if (sdepForm->dbid != MyDatabaseId)
continue;
switch (sdepForm->deptype)
{
- ObjectAddress obj;
- GrantObjectType objtype;
- InternalGrant istmt;
-
/* Shouldn't happen */
case SHARED_DEPENDENCY_PIN:
case SHARED_DEPENDENCY_INVALID:
switch (sdepForm->classid)
{
case RelationRelationId:
- {
- /* is it a sequence or non-sequence? */
- Form_pg_class pg_class_tuple;
- HeapTuple tuple;
-
- tuple = SearchSysCache(RELOID,
- ObjectIdGetDatum(sdepForm->objid),
- 0, 0, 0);
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u",
- sdepForm->objid);
- pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
- if (pg_class_tuple->relkind == RELKIND_SEQUENCE)
- istmt.objtype = ACL_OBJECT_SEQUENCE;
- else
- istmt.objtype = ACL_OBJECT_RELATION;
- ReleaseSysCache(tuple);
+ /* it's OK to use RELATION for a sequence */
+ istmt.objtype = ACL_OBJECT_RELATION;
break;
- }
case DatabaseRelationId:
istmt.objtype = ACL_OBJECT_DATABASE;
break;
default:
elog(ERROR, "unexpected object type %d",
sdepForm->classid);
- /* keep compiler quiet */
- objtype = (GrantObjectType) 0;
break;
}
istmt.is_grant = false;
istmt.objects = list_make1_oid(sdepForm->objid);
istmt.all_privs = true;
istmt.privileges = ACL_NO_RIGHTS;
+ istmt.col_privs = NIL;
istmt.grantees = list_make1_oid(roleid);
istmt.grant_option = false;
istmt.behavior = DROP_CASCADE;
ExecGrantStmt_oids(&istmt);
break;
case SHARED_DEPENDENCY_OWNER:
-
- /*
- * If there's a regular (non-shared) dependency on this
- * object marked with DEPENDENCY_INTERNAL, skip this
- * object. We will drop the referencer object instead.
- */
- if (objectIsInternalDependency(sdepForm->classid, sdepForm->objid))
- continue;
-
- /* Drop the object */
+ /* Save it for deletion below */
obj.classId = sdepForm->classid;
obj.objectId = sdepForm->objid;
- obj.objectSubId = 0;
- performDeletion(&obj, behavior);
+ obj.objectSubId = sdepForm->objsubid;
+ add_exact_object_address(&obj, deleteobjs);
break;
}
}
systable_endscan(scan);
}
- heap_close(sdepRel, AccessExclusiveLock);
+ /* the dependency mechanism does the actual work */
+ performMultipleDeletions(deleteobjs, behavior);
+
+ heap_close(sdepRel, RowExclusiveLock);
+
+ free_object_addresses(deleteobjs);
}
/*
Relation sdepRel;
ListCell *cell;
- sdepRel = heap_open(SharedDependRelationId, AccessShareLock);
+ /*
+ * We don't need this strong a lock here, but we'll call routines that
+ * acquire RowExclusiveLock. Better get that right now to avoid potential
+ * deadlock problems.
+ */
+ sdepRel = heap_open(SharedDependRelationId, RowExclusiveLock);
foreach(cell, roleids)
{
{
Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tuple);
- /* We only operate on objects on the current database */
+ /* We only operate on objects in the current database */
if (sdepForm->dbid != MyDatabaseId)
continue;
if (sdepForm->deptype != SHARED_DEPENDENCY_OWNER)
continue;
- /*
- * If there's a regular (non-shared) dependency on this object
- * marked with DEPENDENCY_INTERNAL, skip this object. We will
- * alter the referencer object instead.
- */
- if (objectIsInternalDependency(sdepForm->classid, sdepForm->objid))
- continue;
-
- /* Issue the appropiate ALTER OWNER call */
+ /* Issue the appropriate ALTER OWNER call */
switch (sdepForm->classid)
{
case ConversionRelationId:
break;
case TypeRelationId:
- AlterTypeOwnerInternal(sdepForm->objid, newrole);
+ AlterTypeOwnerInternal(sdepForm->objid, newrole, true);
break;
case OperatorRelationId:
break;
case RelationRelationId:
- ATExecChangeOwner(sdepForm->objid, newrole, false);
+
+ /*
+ * Pass recursing = true so that we don't fail on indexes,
+ * owned sequences, etc when we happen to visit them
+ * before their parent table.
+ */
+ ATExecChangeOwner(sdepForm->objid, newrole, true);
break;
case ProcedureRelationId:
AlterFunctionOwner_oid(sdepForm->objid, newrole);
break;
+ case LanguageRelationId:
+ AlterLanguageOwner_oid(sdepForm->objid, newrole);
+ break;
+
default:
elog(ERROR, "unexpected classid %d", sdepForm->classid);
break;
systable_endscan(scan);
}
- heap_close(sdepRel, AccessShareLock);
+ heap_close(sdepRel, RowExclusiveLock);
}