diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:46:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:46:48 +0000 |
commit | 311bcfc6b3acdd6fd152798c7f287ddf74fa2a98 (patch) | |
tree | 0ec307299b1dada3701e42f4ca6eda57d708261e /src/backend/catalog/pg_depend.c | |
parent | Initial commit. (diff) | |
download | postgresql-15-upstream.tar.xz postgresql-15-upstream.zip |
Adding upstream version 15.4.upstream/15.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/catalog/pg_depend.c')
-rw-r--r-- | src/backend/catalog/pg_depend.c | 1071 |
1 files changed, 1071 insertions, 0 deletions
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c new file mode 100644 index 0000000..89bbb5c --- /dev/null +++ b/src/backend/catalog/pg_depend.c @@ -0,0 +1,1071 @@ +/*------------------------------------------------------------------------- + * + * pg_depend.c + * routines to support manipulation of the pg_depend relation + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/catalog/pg_depend.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/table.h" +#include "catalog/catalog.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_depend.h" +#include "catalog/pg_extension.h" +#include "commands/extension.h" +#include "miscadmin.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" + + +static bool isObjectPinned(const ObjectAddress *object); + + +/* + * 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; + CatalogIndexState indstate; + TupleTableSlot **slot; + int i, + max_slots, + slot_init_count, + slot_stored_count; + + if (nreferenced <= 0) + return; /* nothing to do */ + + /* + * During bootstrap, do nothing since pg_depend may not exist yet. + * + * Objects created during bootstrap are most likely pinned, and the few + * that are not do not have dependencies on each other, so that there + * would be no need to make a pg_depend entry anyway. + */ + if (IsBootstrapProcessingMode()) + return; + + dependDesc = table_open(DependRelationId, RowExclusiveLock); + + /* + * Allocate the slots to use, but delay costly initialization until we + * know that they will be used. + */ + max_slots = Min(nreferenced, + MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_depend)); + slot = palloc(sizeof(TupleTableSlot *) * max_slots); + + /* Don't open indexes unless we need to make an update */ + indstate = NULL; + + /* number of slots currently storing tuples */ + slot_stored_count = 0; + /* number of slots currently initialized */ + slot_init_count = 0; + 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)) + continue; + + if (slot_init_count < max_slots) + { + slot[slot_stored_count] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc), + &TTSOpsHeapTuple); + slot_init_count++; + } + + ExecClearTuple(slot[slot_stored_count]); + + /* + * Record the dependency. Note we don't bother to check for duplicate + * dependencies; there's no harm in them. + */ + slot[slot_stored_count]->tts_values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId); + slot[slot_stored_count]->tts_values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId); + slot[slot_stored_count]->tts_values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId); + slot[slot_stored_count]->tts_values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior); + slot[slot_stored_count]->tts_values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId); + slot[slot_stored_count]->tts_values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId); + slot[slot_stored_count]->tts_values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId); + + memset(slot[slot_stored_count]->tts_isnull, false, + slot[slot_stored_count]->tts_tupleDescriptor->natts * sizeof(bool)); + + ExecStoreVirtualTuple(slot[slot_stored_count]); + slot_stored_count++; + + /* If slots are full, insert a batch of tuples */ + if (slot_stored_count == max_slots) + { + /* fetch index info only when we know we need it */ + if (indstate == NULL) + indstate = CatalogOpenIndexes(dependDesc); + + CatalogTuplesMultiInsertWithInfo(dependDesc, slot, slot_stored_count, + indstate); + slot_stored_count = 0; + } + } + + /* Insert any tuples left in the buffer */ + if (slot_stored_count > 0) + { + /* fetch index info only when we know we need it */ + if (indstate == NULL) + indstate = CatalogOpenIndexes(dependDesc); + + CatalogTuplesMultiInsertWithInfo(dependDesc, slot, slot_stored_count, + indstate); + } + + if (indstate != NULL) + CatalogCloseIndexes(indstate); + + table_close(dependDesc, RowExclusiveLock); + + /* Drop only the number of slots used */ + for (i = 0; i < slot_init_count; i++) + ExecDropSingleTupleTableSlot(slot[i]); + pfree(slot); +} + +/* + * If we are executing a CREATE EXTENSION operation, mark the given object + * as being a member of the extension, or check that it already is one. + * Otherwise, do nothing. + * + * This must be called during creation of any user-definable object type + * that could be a member of an extension. + * + * isReplace must be true if the object already existed, and false if it is + * newly created. In the former case we insist that it already be a member + * of the current extension. In the latter case we can skip checking whether + * it is already a member of any extension. + * + * Note: isReplace = true is typically used when updating an object in + * CREATE OR REPLACE and similar commands. We used to allow the target + * object to not already be an extension member, instead silently absorbing + * it into the current extension. However, this was both error-prone + * (extensions might accidentally overwrite free-standing objects) and + * a security hazard (since the object would retain its previous ownership). + */ +void +recordDependencyOnCurrentExtension(const ObjectAddress *object, + bool isReplace) +{ + /* Only whole objects can be extension members */ + Assert(object->objectSubId == 0); + + if (creating_extension) + { + ObjectAddress extension; + + /* Only need to check for existing membership if isReplace */ + if (isReplace) + { + Oid oldext; + + /* + * Side note: these catalog lookups are safe only because the + * object is a pre-existing one. In the not-isReplace case, the + * caller has most likely not yet done a CommandCounterIncrement + * that would make the new object visible. + */ + oldext = getExtensionOfObject(object->classId, object->objectId); + if (OidIsValid(oldext)) + { + /* If already a member of this extension, nothing to do */ + if (oldext == CurrentExtensionObject) + return; + /* Already a member of some other extension, so reject */ + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("%s is already a member of extension \"%s\"", + getObjectDescription(object, false), + get_extension_name(oldext)))); + } + /* It's a free-standing object, so reject */ + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("%s is not a member of extension \"%s\"", + getObjectDescription(object, false), + get_extension_name(CurrentExtensionObject)), + errdetail("An extension is not allowed to replace an object that it does not own."))); + } + + /* OK, record it as a member of CurrentExtensionObject */ + extension.classId = ExtensionRelationId; + extension.objectId = CurrentExtensionObject; + extension.objectSubId = 0; + + recordDependencyOn(object, &extension, DEPENDENCY_EXTENSION); + } +} + +/* + * If we are executing a CREATE EXTENSION operation, check that the given + * object is a member of the extension, and throw an error if it isn't. + * Otherwise, do nothing. + * + * This must be called whenever a CREATE IF NOT EXISTS operation (for an + * object type that can be an extension member) has found that an object of + * the desired name already exists. It is insecure for an extension to use + * IF NOT EXISTS except when the conflicting object is already an extension + * member; otherwise a hostile user could substitute an object with arbitrary + * properties. + */ +void +checkMembershipInCurrentExtension(const ObjectAddress *object) +{ + /* + * This is actually the same condition tested in + * recordDependencyOnCurrentExtension; but we want to issue a + * differently-worded error, and anyway it would be pretty confusing to + * call recordDependencyOnCurrentExtension in these circumstances. + */ + + /* Only whole objects can be extension members */ + Assert(object->objectSubId == 0); + + if (creating_extension) + { + Oid oldext; + + oldext = getExtensionOfObject(object->classId, object->objectId); + /* If already a member of this extension, OK */ + if (oldext == CurrentExtensionObject) + return; + /* Else complain */ + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("%s is not a member of extension \"%s\"", + getObjectDescription(object, false), + get_extension_name(CurrentExtensionObject)), + errdetail("An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns."))); + } +} + +/* + * 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). + * + * If skipExtensionDeps is true, we do not delete any dependencies that + * show that the given object is a member of an extension. This avoids + * needing a lot of extra logic to fetch and recreate that dependency. + */ +long +deleteDependencyRecordsFor(Oid classId, Oid objectId, + bool skipExtensionDeps) +{ + long count = 0; + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + + depRel = table_open(DependRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(classId)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + NULL, 2, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + if (skipExtensionDeps && + ((Form_pg_depend) GETSTRUCT(tup))->deptype == DEPENDENCY_EXTENSION) + continue; + + CatalogTupleDelete(depRel, &tup->t_self); + count++; + } + + systable_endscan(scan); + + table_close(depRel, RowExclusiveLock); + + return count; +} + +/* + * deleteDependencyRecordsForClass -- delete all records with given depender + * classId/objectId, dependee classId, and deptype. + * Returns the number of records deleted. + * + * This is a variant of deleteDependencyRecordsFor, useful when revoking + * an object property that is expressed by a dependency record (such as + * extension membership). + */ +long +deleteDependencyRecordsForClass(Oid classId, Oid objectId, + Oid refclassId, char deptype) +{ + long count = 0; + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + + depRel = table_open(DependRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(classId)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + NULL, 2, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + + if (depform->refclassid == refclassId && depform->deptype == deptype) + { + CatalogTupleDelete(depRel, &tup->t_self); + count++; + } + } + + systable_endscan(scan); + + table_close(depRel, RowExclusiveLock); + + return count; +} + +/* + * deleteDependencyRecordsForSpecific -- delete all records with given depender + * classId/objectId, dependee classId/objectId, of the given deptype. + * Returns the number of records deleted. + */ +long +deleteDependencyRecordsForSpecific(Oid classId, Oid objectId, char deptype, + Oid refclassId, Oid refobjectId) +{ + long count = 0; + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + + depRel = table_open(DependRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(classId)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + NULL, 2, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + + if (depform->refclassid == refclassId && + depform->refobjid == refobjectId && + depform->deptype == deptype) + { + CatalogTupleDelete(depRel, &tup->t_self); + count++; + } + } + + systable_endscan(scan); + + table_close(depRel, RowExclusiveLock); + + 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. Also, there is an expectation that we are + * dealing with NORMAL dependencies: if we have to replace an (implicit) + * dependency on a pinned object with an explicit dependency on an unpinned + * one, the new one will be NORMAL. + * + * Returns the number of records updated -- zero indicates a problem. + */ +long +changeDependencyFor(Oid classId, Oid objectId, + Oid refClassId, Oid oldRefObjectId, + Oid newRefObjectId) +{ + long count = 0; + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + ObjectAddress objAddr; + ObjectAddress depAddr; + bool oldIsPinned; + bool newIsPinned; + + /* + * Check to see if either oldRefObjectId or newRefObjectId is pinned. + * Pinned objects should not have any dependency entries pointing to them, + * so in these cases we should add or remove a pg_depend entry, or do + * nothing at all, rather than update an entry as in the normal case. + */ + objAddr.classId = refClassId; + objAddr.objectId = oldRefObjectId; + objAddr.objectSubId = 0; + + oldIsPinned = isObjectPinned(&objAddr); + + objAddr.objectId = newRefObjectId; + + newIsPinned = isObjectPinned(&objAddr); + + if (oldIsPinned) + { + /* + * If both are pinned, we need do nothing. However, return 1 not 0, + * else callers will think this is an error case. + */ + if (newIsPinned) + return 1; + + /* + * There is no old dependency record, but we should insert a new one. + * Assume a normal dependency is wanted. + */ + depAddr.classId = classId; + depAddr.objectId = objectId; + depAddr.objectSubId = 0; + recordDependencyOn(&depAddr, &objAddr, DEPENDENCY_NORMAL); + + return 1; + } + + depRel = table_open(DependRelationId, RowExclusiveLock); + + /* There should be existing dependency record(s), so search. */ + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(classId)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + NULL, 2, key); + + while (HeapTupleIsValid((tup = systable_getnext(scan)))) + { + Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + + if (depform->refclassid == refClassId && + depform->refobjid == oldRefObjectId) + { + if (newIsPinned) + CatalogTupleDelete(depRel, &tup->t_self); + else + { + /* make a modifiable copy */ + tup = heap_copytuple(tup); + depform = (Form_pg_depend) GETSTRUCT(tup); + + depform->refobjid = newRefObjectId; + + CatalogTupleUpdate(depRel, &tup->t_self, tup); + + heap_freetuple(tup); + } + + count++; + } + } + + systable_endscan(scan); + + table_close(depRel, RowExclusiveLock); + + return count; +} + +/* + * Adjust all dependency records to come from a different object of the same type + * + * classId/oldObjectId specify the old referencing object. + * newObjectId is the new referencing object (must be of class classId). + * + * Returns the number of records updated. + */ +long +changeDependenciesOf(Oid classId, Oid oldObjectId, + Oid newObjectId) +{ + long count = 0; + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + + depRel = table_open(DependRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(classId)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(oldObjectId)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + NULL, 2, key); + + while (HeapTupleIsValid((tup = systable_getnext(scan)))) + { + Form_pg_depend depform; + + /* make a modifiable copy */ + tup = heap_copytuple(tup); + depform = (Form_pg_depend) GETSTRUCT(tup); + + depform->objid = newObjectId; + + CatalogTupleUpdate(depRel, &tup->t_self, tup); + + heap_freetuple(tup); + + count++; + } + + systable_endscan(scan); + + table_close(depRel, RowExclusiveLock); + + return count; +} + +/* + * Adjust all dependency records to point to a different object of the same type + * + * refClassId/oldRefObjectId specify the old referenced object. + * newRefObjectId is the new referenced object (must be of class refClassId). + * + * Returns the number of records updated. + */ +long +changeDependenciesOn(Oid refClassId, Oid oldRefObjectId, + Oid newRefObjectId) +{ + long count = 0; + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + ObjectAddress objAddr; + bool newIsPinned; + + depRel = table_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)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot remove dependency on %s because it is a system object", + getObjectDescription(&objAddr, false)))); + + /* + * We can handle adding a dependency on something pinned, though, since + * that just means deleting the dependency entry. + */ + objAddr.objectId = newRefObjectId; + + newIsPinned = isObjectPinned(&objAddr); + + /* Now search for dependency records */ + ScanKeyInit(&key[0], + Anum_pg_depend_refclassid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(refClassId)); + ScanKeyInit(&key[1], + Anum_pg_depend_refobjid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(oldRefObjectId)); + + scan = systable_beginscan(depRel, DependReferenceIndexId, true, + NULL, 2, key); + + while (HeapTupleIsValid((tup = systable_getnext(scan)))) + { + if (newIsPinned) + CatalogTupleDelete(depRel, &tup->t_self); + else + { + Form_pg_depend depform; + + /* make a modifiable copy */ + tup = heap_copytuple(tup); + depform = (Form_pg_depend) GETSTRUCT(tup); + + depform->refobjid = newRefObjectId; + + CatalogTupleUpdate(depRel, &tup->t_self, tup); + + heap_freetuple(tup); + } + + count++; + } + + systable_endscan(scan); + + table_close(depRel, RowExclusiveLock); + + return count; +} + +/* + * isObjectPinned() + * + * Test if an object is required for basic database functionality. + * + * 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) +{ + return IsPinnedObject(object->classId, object->objectId); +} + + +/* + * Various special-purpose lookups and manipulations of pg_depend. + */ + + +/* + * Find the extension containing the specified object, if any + * + * Returns the OID of the extension, or InvalidOid if the object does not + * belong to any extension. + * + * Extension membership is marked by an EXTENSION dependency from the object + * to the extension. Note that the result will be indeterminate if pg_depend + * contains links from this object to more than one extension ... but that + * should never happen. + */ +Oid +getExtensionOfObject(Oid classId, Oid objectId) +{ + Oid result = InvalidOid; + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + + depRel = table_open(DependRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(classId)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + NULL, 2, key); + + while (HeapTupleIsValid((tup = systable_getnext(scan)))) + { + Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + + if (depform->refclassid == ExtensionRelationId && + depform->deptype == DEPENDENCY_EXTENSION) + { + result = depform->refobjid; + break; /* no need to keep scanning */ + } + } + + systable_endscan(scan); + + table_close(depRel, AccessShareLock); + + return result; +} + +/* + * Return (possibly NIL) list of extensions that the given object depends on + * in DEPENDENCY_AUTO_EXTENSION mode. + */ +List * +getAutoExtensionsOfObject(Oid classId, Oid objectId) +{ + List *result = NIL; + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + + depRel = table_open(DependRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(classId)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + NULL, 2, key); + + while (HeapTupleIsValid((tup = systable_getnext(scan)))) + { + Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + + if (depform->refclassid == ExtensionRelationId && + depform->deptype == DEPENDENCY_AUTO_EXTENSION) + result = lappend_oid(result, depform->refobjid); + } + + systable_endscan(scan); + + table_close(depRel, AccessShareLock); + + return result; +} + +/* + * Detect whether a sequence is marked as "owned" by a column + * + * An ownership marker is an AUTO or INTERNAL 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, char deptype, Oid *tableId, int32 *colId) +{ + bool ret = false; + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + + depRel = table_open(DependRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationRelationId)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(seqId)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + NULL, 2, key); + + while (HeapTupleIsValid((tup = systable_getnext(scan)))) + { + Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + + if (depform->refclassid == RelationRelationId && + depform->deptype == deptype) + { + *tableId = depform->refobjid; + *colId = depform->refobjsubid; + ret = true; + break; /* no need to keep scanning */ + } + } + + systable_endscan(scan); + + table_close(depRel, AccessShareLock); + + return ret; +} + +/* + * Collect a list of OIDs of all sequences owned by the specified relation, + * and column if specified. If deptype is not zero, then only find sequences + * with the specified dependency type. + */ +static List * +getOwnedSequences_internal(Oid relid, AttrNumber attnum, char deptype) +{ + List *result = NIL; + Relation depRel; + ScanKeyData key[3]; + SysScanDesc scan; + HeapTuple tup; + + depRel = table_open(DependRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_refclassid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationRelationId)); + ScanKeyInit(&key[1], + Anum_pg_depend_refobjid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + if (attnum) + ScanKeyInit(&key[2], + Anum_pg_depend_refobjsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(attnum)); + + scan = systable_beginscan(depRel, DependReferenceIndexId, true, + NULL, attnum ? 3 : 2, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup); + + /* + * We assume any auto or internal dependency of a sequence on a column + * must be what we are looking for. (We need the relkind test because + * indexes can also have auto dependencies on columns.) + */ + if (deprec->classid == RelationRelationId && + deprec->objsubid == 0 && + deprec->refobjsubid != 0 && + (deprec->deptype == DEPENDENCY_AUTO || deprec->deptype == DEPENDENCY_INTERNAL) && + get_rel_relkind(deprec->objid) == RELKIND_SEQUENCE) + { + if (!deptype || deprec->deptype == deptype) + result = lappend_oid(result, deprec->objid); + } + } + + systable_endscan(scan); + + table_close(depRel, AccessShareLock); + + return result; +} + +/* + * Collect a list of OIDs of all sequences owned (identity or serial) by the + * specified relation. + */ +List * +getOwnedSequences(Oid relid) +{ + return getOwnedSequences_internal(relid, 0, 0); +} + +/* + * Get owned identity sequence, error if not exactly one. + */ +Oid +getIdentitySequence(Oid relid, AttrNumber attnum, bool missing_ok) +{ + List *seqlist = getOwnedSequences_internal(relid, attnum, DEPENDENCY_INTERNAL); + + if (list_length(seqlist) > 1) + elog(ERROR, "more than one owned sequence found"); + else if (list_length(seqlist) < 1) + { + if (missing_ok) + return InvalidOid; + else + elog(ERROR, "no owned sequence found"); + } + + return linitial_oid(seqlist); +} + +/* + * get_index_constraint + * Given the OID of an index, return the OID of the owning unique, + * primary-key, or exclusion constraint, or InvalidOid if there + * is no owning constraint. + */ +Oid +get_index_constraint(Oid indexId) +{ + Oid constraintId = InvalidOid; + Relation depRel; + ScanKeyData key[3]; + SysScanDesc scan; + HeapTuple tup; + + /* Search the dependency table for the index */ + depRel = table_open(DependRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationRelationId)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(indexId)); + ScanKeyInit(&key[2], + Anum_pg_depend_objsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(0)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + NULL, 3, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + 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; + } + } + + systable_endscan(scan); + table_close(depRel, AccessShareLock); + + return constraintId; +} + +/* + * get_index_ref_constraints + * Given the OID of an index, return the OID of all foreign key + * constraints which reference the index. + */ +List * +get_index_ref_constraints(Oid indexId) +{ + List *result = NIL; + Relation depRel; + ScanKeyData key[3]; + SysScanDesc scan; + HeapTuple tup; + + /* Search the dependency table for the index */ + depRel = table_open(DependRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_refclassid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationRelationId)); + ScanKeyInit(&key[1], + Anum_pg_depend_refobjid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(indexId)); + ScanKeyInit(&key[2], + Anum_pg_depend_refobjsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(0)); + + scan = systable_beginscan(depRel, DependReferenceIndexId, true, + NULL, 3, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup); + + /* + * We assume any normal dependency from a constraint must be what we + * are looking for. + */ + if (deprec->classid == ConstraintRelationId && + deprec->objsubid == 0 && + deprec->deptype == DEPENDENCY_NORMAL) + { + result = lappend_oid(result, deprec->objid); + } + } + + systable_endscan(scan); + table_close(depRel, AccessShareLock); + + return result; +} |