blob: 63b27107172f8eb06524aab2eb0a59db42f95795 [file] [log] [blame]
/*-------------------------------------------------------------------------
*
* pg_depend.c
* routines to support manipulation of the pg_depend relation
*
* 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_depend.c,v 1.23 2006/10/04 00:29:50 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/catquery.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "miscadmin.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/tqual.h"
static bool isObjectPinned(const ObjectAddress *object, Relation rel);
/*
* 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_depend, without any other processing.
*/
void
recordDependencyOn(const ObjectAddress *depender,
const ObjectAddress *referenced,
DependencyType behavior)
{
recordMultipleDependencies(depender, referenced, 1, behavior);
}
/*
* Record multiple dependencies (of the same kind) for a single dependent
* object. This has a little less overhead than recording each separately.
*/
void
recordMultipleDependencies(const ObjectAddress *depender,
const ObjectAddress *referenced,
int nreferenced,
DependencyType behavior)
{
Relation dependDesc;
HeapTuple tup;
int i;
bool nulls[Natts_pg_depend];
Datum values[Natts_pg_depend];
cqContext cqc;
cqContext *pcqCtx;
if (nreferenced <= 0)
return; /* nothing to do */
/*
* During bootstrap, do nothing since pg_depend may not exist yet. initdb
* will fill in appropriate pg_depend entries after bootstrap.
*/
if (IsBootstrapProcessingMode())
return;
dependDesc = heap_open(DependRelationId, RowExclusiveLock);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), dependDesc),
cql("INSERT INTO pg_depend ",
NULL));
memset(nulls, false, sizeof(nulls));
for (i = 0; i < nreferenced; i++, referenced++)
{
/*
* If the referenced object is pinned by the system, there's no real
* need to record dependencies on it. This saves lots of space in
* pg_depend, so it's worth the time taken to check.
*/
if (!isObjectPinned(referenced, dependDesc))
{
/*
* Record the Dependency. Note we don't bother to check for
* duplicate dependencies; there's no harm in them.
*/
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_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
tup = caql_form_tuple(pcqCtx, values, nulls);
caql_insert(pcqCtx, tup);
/* and Update indexes (implicit) */
heap_freetuple(tup);
}
}
caql_endscan(pcqCtx);
heap_close(dependDesc, RowExclusiveLock);
}
/*
* deleteDependencyRecordsFor -- delete all records with given depender
* classId/objectId. Returns the number of records deleted.
*
* This is used when redefining an existing object. Links leading to the
* object do not change, and links leading from it will be recreated
* (possibly with some differences from before).
*/
long
deleteDependencyRecordsFor(Oid classId, Oid objectId)
{
long count = 0;
count = caql_getcount(
NULL,
cql("DELETE FROM pg_depend "
" WHERE classid = :1 "
" AND objid = :2 ",
ObjectIdGetDatum(classId),
ObjectIdGetDatum(objectId)));
return count;
}
/*
* Adjust dependency record(s) to point to a different object of the same type
*
* classId/objectId specify the referencing object.
* refClassId/oldRefObjectId specify the old referenced object.
* newRefObjectId is the new referenced object (must be of class refClassId).
*
* Note the lack of objsubid parameters. If there are subobject references
* they will all be readjusted.
*
* Returns the number of records updated.
*/
long
changeDependencyFor(Oid classId, Oid objectId,
Oid refClassId, Oid oldRefObjectId,
Oid newRefObjectId)
{
long count = 0;
Relation depRel;
cqContext *pcqCtx;
cqContext cqc;
HeapTuple tup;
ObjectAddress objAddr;
bool newIsPinned;
depRel = heap_open(DependRelationId, RowExclusiveLock);
/*
* If oldRefObjectId is pinned, there won't be any dependency entries on
* it --- we can't cope in that case. (This isn't really worth expending
* code to fix, in current usage; it just means you can't rename stuff out
* of pg_catalog, which would likely be a bad move anyway.)
*/
objAddr.classId = refClassId;
objAddr.objectId = oldRefObjectId;
objAddr.objectSubId = 0;
if (isObjectPinned(&objAddr, depRel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot remove dependency on %s because it is a system object",
getObjectDescription(&objAddr))));
/*
* We can handle adding a dependency on something pinned, though, since
* that just means deleting the dependency entry.
*/
objAddr.objectId = newRefObjectId;
newIsPinned = isObjectPinned(&objAddr, depRel);
/* Now search for dependency records */
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), depRel),
cql("SELECT * FROM pg_depend "
" WHERE classid = :1 "
" AND objid = :2 "
" FOR UPDATE ",
ObjectIdGetDatum(classId),
ObjectIdGetDatum(objectId)));
while (HeapTupleIsValid((tup = caql_getnext(pcqCtx))))
{
Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup);
if (depform->refclassid == refClassId &&
depform->refobjid == oldRefObjectId)
{
if (newIsPinned)
caql_delete_current(pcqCtx);
else
{
/* make a modifiable copy */
tup = heap_copytuple(tup);
depform = (Form_pg_depend) GETSTRUCT(tup);
depform->refobjid = newRefObjectId;
caql_update_current(pcqCtx, tup);
heap_freetuple(tup);
}
count++;
}
}
caql_endscan(pcqCtx);
heap_close(depRel, RowExclusiveLock);
return count;
}
/*
* isObjectPinned()
*
* Test if an object is required for basic database functionality.
* Caller must already have opened pg_depend.
*
* The passed subId, if any, is ignored; we assume that only whole objects
* are pinned (and that this implies pinning their components).
*/
static bool
isObjectPinned(const ObjectAddress *object, Relation rel)
{
bool ret = false;
cqContext cqc;
HeapTuple tup;
tup = caql_getfirst(
caql_addrel(cqclr(&cqc), rel),
cql("SELECT * FROM pg_depend "
" WHERE refclassid = :1 "
" AND refobjid = :2 ",
ObjectIdGetDatum(object->classId),
ObjectIdGetDatum(object->objectId)));
/*
* Since we won't generate additional pg_depend entries for pinned
* objects, there can be at most one entry referencing a pinned object.
* Hence, it's sufficient to look at the first returned tuple; we don't
* need to loop.
*/
if (HeapTupleIsValid(tup))
{
Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
if (foundDep->deptype == DEPENDENCY_PIN)
ret = true;
}
return ret;
}
/*
* Various special-purpose lookups and manipulations of pg_depend.
*/
/*
* Detect whether a sequence is marked as "owned" by a column
*
* An ownership marker is an AUTO dependency from the sequence to the
* column. If we find one, store the identity of the owning column
* into *tableId and *colId and return TRUE; else return FALSE.
*
* Note: if there's more than one such pg_depend entry then you get
* a random one of them returned into the out parameters. This should
* not happen, though.
*/
bool
sequenceIsOwned(Oid seqId, Oid *tableId, int32 *colId)
{
bool ret = false;
cqContext *pcqCtx;
HeapTuple tup;
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_depend "
" WHERE classid = :1 "
" AND objid = :2 ",
ObjectIdGetDatum(RelationRelationId),
ObjectIdGetDatum(seqId)));
while (HeapTupleIsValid((tup = caql_getnext(pcqCtx))))
{
Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup);
if (depform->refclassid == RelationRelationId &&
depform->deptype == DEPENDENCY_AUTO)
{
*tableId = depform->refobjid;
*colId = depform->refobjsubid;
ret = true;
break; /* no need to keep scanning */
}
}
caql_endscan(pcqCtx);
return ret;
}
/*
* Remove any existing "owned" markers for the specified sequence.
*
* Note: we don't provide a special function to install an "owned"
* marker; just use recordDependencyOn().
*/
void
markSequenceUnowned(Oid seqId)
{
cqContext *pcqCtx;
HeapTuple tup;
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_depend "
" WHERE classid = :1 "
" AND objid = :2 "
" FOR UPDATE ",
ObjectIdGetDatum(RelationRelationId),
ObjectIdGetDatum(seqId)));
while (HeapTupleIsValid((tup = caql_getnext(pcqCtx))))
{
Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup);
if (depform->refclassid == RelationRelationId &&
depform->deptype == DEPENDENCY_AUTO)
{
caql_delete_current(pcqCtx);
}
}
caql_endscan(pcqCtx);
}
/*
* get_constraint_index
* Given the OID of a unique or primary-key constraint, return the
* OID of the underlying unique index.
*
* Return InvalidOid if the index couldn't be found; this suggests the
* given OID is bogus, but we leave it to caller to decide what to do.
*/
Oid
get_constraint_index(Oid constraintId)
{
Oid indexId = InvalidOid;
HeapTuple tup;
cqContext *pcqCtx;
/* Search the dependency table for the dependent index */
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_depend "
" WHERE refclassid = :1 "
" AND refobjid = :2 "
" AND refobjsubid = :3 ",
ObjectIdGetDatum(ConstraintRelationId),
ObjectIdGetDatum(constraintId),
Int32GetDatum(0)));
while (HeapTupleIsValid(tup = caql_getnext(pcqCtx)))
{
Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup);
/*
* We assume any internal dependency of an index on the constraint
* must be what we are looking for. (The relkind test is just
* paranoia; there shouldn't be any such dependencies otherwise.)
*/
if (deprec->classid == RelationRelationId &&
deprec->objsubid == 0 &&
deprec->deptype == DEPENDENCY_INTERNAL &&
get_rel_relkind(deprec->objid) == RELKIND_INDEX)
{
indexId = deprec->objid;
break;
}
}
caql_endscan(pcqCtx);
return indexId;
}
/*
* get_index_constraint
* Given the OID of an index, return the OID of the owning unique or
* primary-key constraint, or InvalidOid if no such constraint.
*/
Oid
get_index_constraint(Oid indexId)
{
Oid constraintId = InvalidOid;
HeapTuple tup;
cqContext *pcqCtx;
/* Search the dependency table for the index */
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_depend "
" WHERE classid = :1 "
" AND objid = :2 "
" AND objsubid = :3 ",
ObjectIdGetDatum(RelationRelationId),
ObjectIdGetDatum(indexId),
Int32GetDatum(0)));
while (HeapTupleIsValid(tup = caql_getnext(pcqCtx)))
{
Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup);
/*
* We assume any internal dependency on a constraint must be what we
* are looking for.
*/
if (deprec->refclassid == ConstraintRelationId &&
deprec->refobjsubid == 0 &&
deprec->deptype == DEPENDENCY_INTERNAL)
{
constraintId = deprec->refobjid;
break;
}
}
caql_endscan(pcqCtx);
return constraintId;
}