2002-07-12 20:43:19 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* pg_depend.c
|
|
|
|
* routines to support manipulation of the pg_depend relation
|
|
|
|
*
|
2021-01-02 19:06:25 +01:00
|
|
|
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
|
2002-07-12 20:43:19 +02:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2010-09-20 22:08:53 +02:00
|
|
|
* src/backend/catalog/pg_depend.c
|
2002-07-12 20:43:19 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
|
2019-12-27 00:09:00 +01:00
|
|
|
#include "access/genam.h"
|
2012-08-30 22:15:44 +02:00
|
|
|
#include "access/htup_details.h"
|
2019-01-21 19:18:20 +01:00
|
|
|
#include "access/table.h"
|
2021-07-15 17:41:47 +02:00
|
|
|
#include "catalog/catalog.h"
|
2002-07-12 20:43:19 +02:00
|
|
|
#include "catalog/dependency.h"
|
2006-07-11 19:26:59 +02:00
|
|
|
#include "catalog/indexing.h"
|
2007-12-02 00:44:44 +01:00
|
|
|
#include "catalog/pg_constraint.h"
|
2002-07-12 20:43:19 +02:00
|
|
|
#include "catalog/pg_depend.h"
|
2011-02-08 22:08:41 +01:00
|
|
|
#include "catalog/pg_extension.h"
|
|
|
|
#include "commands/extension.h"
|
2002-07-12 20:43:19 +02:00
|
|
|
#include "miscadmin.h"
|
|
|
|
#include "utils/fmgroids.h"
|
2007-12-02 00:44:44 +01:00
|
|
|
#include "utils/lsyscache.h"
|
2008-06-19 02:46:06 +02:00
|
|
|
#include "utils/rel.h"
|
2002-07-12 20:43:19 +02:00
|
|
|
|
|
|
|
|
2021-07-15 17:41:47 +02:00
|
|
|
static bool isObjectPinned(const ObjectAddress *object);
|
2002-07-12 20:43:19 +02:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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)
|
2002-07-16 07:53:34 +02:00
|
|
|
{
|
2021-05-07 10:17:42 +02:00
|
|
|
recordMultipleDependencies(depender, referenced, 1, behavior);
|
2002-07-16 07:53:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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,
|
2021-05-07 10:17:42 +02:00
|
|
|
DependencyType behavior)
|
2002-07-12 20:43:19 +02:00
|
|
|
{
|
|
|
|
Relation dependDesc;
|
2002-08-05 05:29:17 +02:00
|
|
|
CatalogIndexState indstate;
|
2020-09-05 06:52:47 +02:00
|
|
|
TupleTableSlot **slot;
|
|
|
|
int i,
|
|
|
|
max_slots,
|
|
|
|
slot_init_count,
|
|
|
|
slot_stored_count;
|
2002-07-16 07:53:34 +02:00
|
|
|
|
|
|
|
if (nreferenced <= 0)
|
|
|
|
return; /* nothing to do */
|
2002-07-12 20:43:19 +02:00
|
|
|
|
|
|
|
/*
|
2021-07-15 17:41:47 +02:00
|
|
|
* 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.
|
2002-07-12 20:43:19 +02:00
|
|
|
*/
|
|
|
|
if (IsBootstrapProcessingMode())
|
|
|
|
return;
|
|
|
|
|
2019-01-21 19:32:19 +01:00
|
|
|
dependDesc = table_open(DependRelationId, RowExclusiveLock);
|
2002-07-12 20:43:19 +02:00
|
|
|
|
2020-09-05 06:52:47 +02:00
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
|
2002-08-05 05:29:17 +02:00
|
|
|
/* Don't open indexes unless we need to make an update */
|
|
|
|
indstate = NULL;
|
|
|
|
|
2020-09-05 06:52:47 +02:00
|
|
|
/* number of slots currently storing tuples */
|
|
|
|
slot_stored_count = 0;
|
|
|
|
/* number of slots currently initialized */
|
|
|
|
slot_init_count = 0;
|
2002-07-16 07:53:34 +02:00
|
|
|
for (i = 0; i < nreferenced; i++, referenced++)
|
2002-07-12 20:43:19 +02:00
|
|
|
{
|
|
|
|
/*
|
2002-07-16 07:53:34 +02:00
|
|
|
* If the referenced object is pinned by the system, there's no real
|
2021-05-07 10:17:42 +02:00
|
|
|
* need to record dependencies on it. This saves lots of space in
|
|
|
|
* pg_depend, so it's worth the time taken to check.
|
2002-07-12 20:43:19 +02:00
|
|
|
*/
|
2021-07-15 17:41:47 +02:00
|
|
|
if (isObjectPinned(referenced))
|
2020-09-05 06:52:47 +02:00
|
|
|
continue;
|
2002-07-16 07:53:34 +02:00
|
|
|
|
2020-09-05 06:52:47 +02:00
|
|
|
if (slot_init_count < max_slots)
|
|
|
|
{
|
|
|
|
slot[slot_stored_count] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
|
|
|
|
&TTSOpsHeapTuple);
|
|
|
|
slot_init_count++;
|
|
|
|
}
|
2002-07-16 07:53:34 +02:00
|
|
|
|
2020-09-05 06:52:47 +02:00
|
|
|
ExecClearTuple(slot[slot_stored_count]);
|
2002-07-16 07:53:34 +02:00
|
|
|
|
2020-09-05 06:52:47 +02:00
|
|
|
/*
|
|
|
|
* 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);
|
2021-05-07 10:17:42 +02:00
|
|
|
|
|
|
|
memset(slot[slot_stored_count]->tts_isnull, false,
|
|
|
|
slot[slot_stored_count]->tts_tupleDescriptor->natts * sizeof(bool));
|
2020-09-05 06:52:47 +02:00
|
|
|
|
|
|
|
ExecStoreVirtualTuple(slot[slot_stored_count]);
|
|
|
|
slot_stored_count++;
|
|
|
|
|
|
|
|
/* If slots are full, insert a batch of tuples */
|
|
|
|
if (slot_stored_count == max_slots)
|
|
|
|
{
|
2017-02-01 23:18:36 +01:00
|
|
|
/* fetch index info only when we know we need it */
|
2002-08-05 05:29:17 +02:00
|
|
|
if (indstate == NULL)
|
|
|
|
indstate = CatalogOpenIndexes(dependDesc);
|
|
|
|
|
2020-09-05 06:52:47 +02:00
|
|
|
CatalogTuplesMultiInsertWithInfo(dependDesc, slot, slot_stored_count,
|
|
|
|
indstate);
|
|
|
|
slot_stored_count = 0;
|
2002-07-12 20:43:19 +02:00
|
|
|
}
|
2002-07-16 07:53:34 +02:00
|
|
|
}
|
2002-07-12 20:43:19 +02:00
|
|
|
|
2020-09-05 06:52:47 +02:00
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
|
2002-08-05 05:29:17 +02:00
|
|
|
if (indstate != NULL)
|
|
|
|
CatalogCloseIndexes(indstate);
|
2002-07-12 20:43:19 +02:00
|
|
|
|
2019-01-21 19:32:19 +01:00
|
|
|
table_close(dependDesc, RowExclusiveLock);
|
2020-09-05 06:52:47 +02:00
|
|
|
|
|
|
|
/* Drop only the number of slots used */
|
|
|
|
for (i = 0; i < slot_init_count; i++)
|
|
|
|
ExecDropSingleTupleTableSlot(slot[i]);
|
|
|
|
pfree(slot);
|
2002-07-12 20:43:19 +02:00
|
|
|
}
|
|
|
|
|
2011-02-08 22:08:41 +01:00
|
|
|
/*
|
|
|
|
* If we are executing a CREATE EXTENSION operation, mark the given object
|
|
|
|
* as being a member of the extension. Otherwise, do nothing.
|
|
|
|
*
|
|
|
|
* This must be called during creation of any user-definable object type
|
|
|
|
* that could be a member of an extension.
|
2011-07-23 22:59:39 +02:00
|
|
|
*
|
|
|
|
* If isReplace is true, the object already existed (or might have already
|
|
|
|
* existed), so we must check for a pre-existing extension membership entry.
|
|
|
|
* Passing false is a guarantee that the object is newly created, and so
|
|
|
|
* could not already be a member of any extension.
|
Prevent ALTER TYPE/DOMAIN/OPERATOR from changing extension membership.
If recordDependencyOnCurrentExtension is invoked on a pre-existing,
free-standing object during an extension update script, that object
will become owned by the extension. In our current code this is
possible in three cases:
* Replacing a "shell" type or operator.
* CREATE OR REPLACE overwriting an existing object.
* ALTER TYPE SET, ALTER DOMAIN SET, and ALTER OPERATOR SET.
The first of these cases is intentional behavior, as noted by the
existing comments for GenerateTypeDependencies. It seems like
appropriate behavior for CREATE OR REPLACE too; at least, the obvious
alternatives are not better. However, the fact that it happens during
ALTER is an artifact of trying to share code (GenerateTypeDependencies
and makeOperatorDependencies) between the CREATE and ALTER cases.
Since an extension script would be unlikely to ALTER an object that
didn't already belong to the extension, this behavior is not very
troubling for the direct target object ... but ALTER TYPE SET will
recurse to dependent domains, and it is very uncool for those to
become owned by the extension if they were not already.
Let's fix this by redefining the ALTER cases to never change extension
membership, full stop. We could minimize the behavioral change by
only changing the behavior when ALTER TYPE SET is recursing to a
domain, but that would complicate the code and it does not seem like
a better definition.
Per bug #17144 from Alex Kozhemyakin. Back-patch to v13 where ALTER
TYPE SET was added. (The other cases are older, but since they only
affect the directly-named object, there's not enough of a problem to
justify changing the behavior further back.)
Discussion: https://postgr.es/m/17144-e67d7a8f049de9af@postgresql.org
2021-08-17 20:29:22 +02:00
|
|
|
*
|
2021-12-09 07:20:36 +01:00
|
|
|
* Note: isReplace = true is typically used when updating an object in
|
Prevent ALTER TYPE/DOMAIN/OPERATOR from changing extension membership.
If recordDependencyOnCurrentExtension is invoked on a pre-existing,
free-standing object during an extension update script, that object
will become owned by the extension. In our current code this is
possible in three cases:
* Replacing a "shell" type or operator.
* CREATE OR REPLACE overwriting an existing object.
* ALTER TYPE SET, ALTER DOMAIN SET, and ALTER OPERATOR SET.
The first of these cases is intentional behavior, as noted by the
existing comments for GenerateTypeDependencies. It seems like
appropriate behavior for CREATE OR REPLACE too; at least, the obvious
alternatives are not better. However, the fact that it happens during
ALTER is an artifact of trying to share code (GenerateTypeDependencies
and makeOperatorDependencies) between the CREATE and ALTER cases.
Since an extension script would be unlikely to ALTER an object that
didn't already belong to the extension, this behavior is not very
troubling for the direct target object ... but ALTER TYPE SET will
recurse to dependent domains, and it is very uncool for those to
become owned by the extension if they were not already.
Let's fix this by redefining the ALTER cases to never change extension
membership, full stop. We could minimize the behavioral change by
only changing the behavior when ALTER TYPE SET is recursing to a
domain, but that would complicate the code and it does not seem like
a better definition.
Per bug #17144 from Alex Kozhemyakin. Back-patch to v13 where ALTER
TYPE SET was added. (The other cases are older, but since they only
affect the directly-named object, there's not enough of a problem to
justify changing the behavior further back.)
Discussion: https://postgr.es/m/17144-e67d7a8f049de9af@postgresql.org
2021-08-17 20:29:22 +02:00
|
|
|
* CREATE OR REPLACE and similar commands. The net effect is that if an
|
|
|
|
* extension script uses such a command on a pre-existing free-standing
|
|
|
|
* object, the object will be absorbed into the extension. If the object
|
|
|
|
* is already a member of some other extension, the command will fail.
|
|
|
|
* This behavior is desirable for cases such as replacing a shell type.
|
2011-02-08 22:08:41 +01:00
|
|
|
*/
|
|
|
|
void
|
2011-07-23 22:59:39 +02:00
|
|
|
recordDependencyOnCurrentExtension(const ObjectAddress *object,
|
|
|
|
bool isReplace)
|
2011-02-08 22:08:41 +01:00
|
|
|
{
|
2011-07-23 22:59:39 +02:00
|
|
|
/* Only whole objects can be extension members */
|
|
|
|
Assert(object->objectSubId == 0);
|
|
|
|
|
2011-02-08 22:08:41 +01:00
|
|
|
if (creating_extension)
|
|
|
|
{
|
|
|
|
ObjectAddress extension;
|
|
|
|
|
2011-07-23 22:59:39 +02:00
|
|
|
/* Only need to check for existing membership if isReplace */
|
|
|
|
if (isReplace)
|
|
|
|
{
|
|
|
|
Oid oldext;
|
|
|
|
|
|
|
|
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\"",
|
2020-07-15 02:03:10 +02:00
|
|
|
getObjectDescription(object, false),
|
2011-07-23 22:59:39 +02:00
|
|
|
get_extension_name(oldext))));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* OK, record it as a member of CurrentExtensionObject */
|
2011-02-08 22:08:41 +01:00
|
|
|
extension.classId = ExtensionRelationId;
|
|
|
|
extension.objectId = CurrentExtensionObject;
|
|
|
|
extension.objectSubId = 0;
|
|
|
|
|
|
|
|
recordDependencyOn(object, &extension, DEPENDENCY_EXTENSION);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-07-17 00:12:20 +02:00
|
|
|
/*
|
|
|
|
* deleteDependencyRecordsFor -- delete all records with given depender
|
2002-08-11 23:17:35 +02:00
|
|
|
* classId/objectId. Returns the number of records deleted.
|
2002-07-17 00:12:20 +02:00
|
|
|
*
|
|
|
|
* 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).
|
2011-02-08 22:08:41 +01:00
|
|
|
*
|
|
|
|
* 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.
|
2002-07-17 00:12:20 +02:00
|
|
|
*/
|
2002-08-11 23:17:35 +02:00
|
|
|
long
|
2011-02-08 22:08:41 +01:00
|
|
|
deleteDependencyRecordsFor(Oid classId, Oid objectId,
|
|
|
|
bool skipExtensionDeps)
|
2002-07-17 00:12:20 +02:00
|
|
|
{
|
2002-08-11 23:17:35 +02:00
|
|
|
long count = 0;
|
2002-07-17 00:12:20 +02:00
|
|
|
Relation depRel;
|
|
|
|
ScanKeyData key[2];
|
|
|
|
SysScanDesc scan;
|
|
|
|
HeapTuple tup;
|
|
|
|
|
2019-01-21 19:32:19 +01:00
|
|
|
depRel = table_open(DependRelationId, RowExclusiveLock);
|
2002-07-17 00:12:20 +02:00
|
|
|
|
2003-11-12 22:15:59 +01:00
|
|
|
ScanKeyInit(&key[0],
|
|
|
|
Anum_pg_depend_classid,
|
|
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
|
|
ObjectIdGetDatum(classId));
|
|
|
|
ScanKeyInit(&key[1],
|
|
|
|
Anum_pg_depend_objid,
|
|
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
|
|
ObjectIdGetDatum(objectId));
|
2002-07-17 00:12:20 +02:00
|
|
|
|
2005-04-14 22:03:27 +02:00
|
|
|
scan = systable_beginscan(depRel, DependDependerIndexId, true,
|
Use an MVCC snapshot, rather than SnapshotNow, for catalog scans.
SnapshotNow scans have the undesirable property that, in the face of
concurrent updates, the scan can fail to see either the old or the new
versions of the row. In many cases, we work around this by requiring
DDL operations to hold AccessExclusiveLock on the object being
modified; in some cases, the existing locking is inadequate and random
failures occur as a result. This commit doesn't change anything
related to locking, but will hopefully pave the way to allowing lock
strength reductions in the future.
The major issue has held us back from making this change in the past
is that taking an MVCC snapshot is significantly more expensive than
using a static special snapshot such as SnapshotNow. However, testing
of various worst-case scenarios reveals that this problem is not
severe except under fairly extreme workloads. To mitigate those
problems, we avoid retaking the MVCC snapshot for each new scan;
instead, we take a new snapshot only when invalidation messages have
been processed. The catcache machinery already requires that
invalidation messages be sent before releasing the related heavyweight
lock; else other backends might rely on locally-cached data rather
than scanning the catalog at all. Thus, making snapshot reuse
dependent on the same guarantees shouldn't break anything that wasn't
already subtly broken.
Patch by me. Review by Michael Paquier and Andres Freund.
2013-07-02 15:47:01 +02:00
|
|
|
NULL, 2, key);
|
2002-07-17 00:12:20 +02:00
|
|
|
|
|
|
|
while (HeapTupleIsValid(tup = systable_getnext(scan)))
|
|
|
|
{
|
2011-02-08 22:08:41 +01:00
|
|
|
if (skipExtensionDeps &&
|
|
|
|
((Form_pg_depend) GETSTRUCT(tup))->deptype == DEPENDENCY_EXTENSION)
|
|
|
|
continue;
|
|
|
|
|
2017-02-01 22:13:30 +01:00
|
|
|
CatalogTupleDelete(depRel, &tup->t_self);
|
2002-08-11 23:17:35 +02:00
|
|
|
count++;
|
2002-07-17 00:12:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
systable_endscan(scan);
|
|
|
|
|
2019-01-21 19:32:19 +01:00
|
|
|
table_close(depRel, RowExclusiveLock);
|
2002-08-11 23:17:35 +02:00
|
|
|
|
|
|
|
return count;
|
2002-07-17 00:12:20 +02:00
|
|
|
}
|
2002-07-12 20:43:19 +02:00
|
|
|
|
2011-02-10 23:36:44 +01:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
|
2019-01-21 19:32:19 +01:00
|
|
|
depRel = table_open(DependRelationId, RowExclusiveLock);
|
2011-02-10 23:36:44 +01:00
|
|
|
|
|
|
|
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,
|
Use an MVCC snapshot, rather than SnapshotNow, for catalog scans.
SnapshotNow scans have the undesirable property that, in the face of
concurrent updates, the scan can fail to see either the old or the new
versions of the row. In many cases, we work around this by requiring
DDL operations to hold AccessExclusiveLock on the object being
modified; in some cases, the existing locking is inadequate and random
failures occur as a result. This commit doesn't change anything
related to locking, but will hopefully pave the way to allowing lock
strength reductions in the future.
The major issue has held us back from making this change in the past
is that taking an MVCC snapshot is significantly more expensive than
using a static special snapshot such as SnapshotNow. However, testing
of various worst-case scenarios reveals that this problem is not
severe except under fairly extreme workloads. To mitigate those
problems, we avoid retaking the MVCC snapshot for each new scan;
instead, we take a new snapshot only when invalidation messages have
been processed. The catcache machinery already requires that
invalidation messages be sent before releasing the related heavyweight
lock; else other backends might rely on locally-cached data rather
than scanning the catalog at all. Thus, making snapshot reuse
dependent on the same guarantees shouldn't break anything that wasn't
already subtly broken.
Patch by me. Review by Michael Paquier and Andres Freund.
2013-07-02 15:47:01 +02:00
|
|
|
NULL, 2, key);
|
2011-02-10 23:36:44 +01:00
|
|
|
|
|
|
|
while (HeapTupleIsValid(tup = systable_getnext(scan)))
|
|
|
|
{
|
|
|
|
Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup);
|
|
|
|
|
|
|
|
if (depform->refclassid == refclassId && depform->deptype == deptype)
|
|
|
|
{
|
2017-02-01 22:13:30 +01:00
|
|
|
CatalogTupleDelete(depRel, &tup->t_self);
|
2011-02-10 23:36:44 +01:00
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
systable_endscan(scan);
|
|
|
|
|
2019-01-21 19:32:19 +01:00
|
|
|
table_close(depRel, RowExclusiveLock);
|
2011-02-10 23:36:44 +01:00
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2020-04-20 19:42:12 +02:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2005-08-01 06:03:59 +02:00
|
|
|
/*
|
|
|
|
* 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
|
2019-02-10 00:08:48 +01:00
|
|
|
* 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.
|
2005-08-01 06:03:59 +02:00
|
|
|
*
|
2019-02-10 00:08:48 +01:00
|
|
|
* Returns the number of records updated -- zero indicates a problem.
|
2005-08-01 06:03:59 +02:00
|
|
|
*/
|
|
|
|
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;
|
2019-02-10 00:08:48 +01:00
|
|
|
ObjectAddress depAddr;
|
|
|
|
bool oldIsPinned;
|
2005-08-01 06:03:59 +02:00
|
|
|
bool newIsPinned;
|
|
|
|
|
|
|
|
/*
|
2019-02-10 00:08:48 +01:00
|
|
|
* 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.
|
2005-08-01 06:03:59 +02:00
|
|
|
*/
|
|
|
|
objAddr.classId = refClassId;
|
|
|
|
objAddr.objectId = oldRefObjectId;
|
|
|
|
objAddr.objectSubId = 0;
|
|
|
|
|
2021-07-15 17:41:47 +02:00
|
|
|
oldIsPinned = isObjectPinned(&objAddr);
|
2005-08-01 06:03:59 +02:00
|
|
|
|
|
|
|
objAddr.objectId = newRefObjectId;
|
|
|
|
|
2021-07-15 17:41:47 +02:00
|
|
|
newIsPinned = isObjectPinned(&objAddr);
|
2005-08-01 06:03:59 +02:00
|
|
|
|
2019-02-10 00:08:48 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-07-15 17:41:47 +02:00
|
|
|
depRel = table_open(DependRelationId, RowExclusiveLock);
|
|
|
|
|
2019-02-10 00:08:48 +01:00
|
|
|
/* There should be existing dependency record(s), so search. */
|
2005-08-01 06:03:59 +02:00
|
|
|
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,
|
Use an MVCC snapshot, rather than SnapshotNow, for catalog scans.
SnapshotNow scans have the undesirable property that, in the face of
concurrent updates, the scan can fail to see either the old or the new
versions of the row. In many cases, we work around this by requiring
DDL operations to hold AccessExclusiveLock on the object being
modified; in some cases, the existing locking is inadequate and random
failures occur as a result. This commit doesn't change anything
related to locking, but will hopefully pave the way to allowing lock
strength reductions in the future.
The major issue has held us back from making this change in the past
is that taking an MVCC snapshot is significantly more expensive than
using a static special snapshot such as SnapshotNow. However, testing
of various worst-case scenarios reveals that this problem is not
severe except under fairly extreme workloads. To mitigate those
problems, we avoid retaking the MVCC snapshot for each new scan;
instead, we take a new snapshot only when invalidation messages have
been processed. The catcache machinery already requires that
invalidation messages be sent before releasing the related heavyweight
lock; else other backends might rely on locally-cached data rather
than scanning the catalog at all. Thus, making snapshot reuse
dependent on the same guarantees shouldn't break anything that wasn't
already subtly broken.
Patch by me. Review by Michael Paquier and Andres Freund.
2013-07-02 15:47:01 +02:00
|
|
|
NULL, 2, key);
|
2005-08-01 06:03:59 +02:00
|
|
|
|
|
|
|
while (HeapTupleIsValid((tup = systable_getnext(scan))))
|
|
|
|
{
|
|
|
|
Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup);
|
|
|
|
|
|
|
|
if (depform->refclassid == refClassId &&
|
|
|
|
depform->refobjid == oldRefObjectId)
|
|
|
|
{
|
|
|
|
if (newIsPinned)
|
2017-02-01 22:13:30 +01:00
|
|
|
CatalogTupleDelete(depRel, &tup->t_self);
|
2005-08-01 06:03:59 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
/* make a modifiable copy */
|
|
|
|
tup = heap_copytuple(tup);
|
|
|
|
depform = (Form_pg_depend) GETSTRUCT(tup);
|
|
|
|
|
|
|
|
depform->refobjid = newRefObjectId;
|
|
|
|
|
2017-01-31 22:42:24 +01:00
|
|
|
CatalogTupleUpdate(depRel, &tup->t_self, tup);
|
2005-08-01 06:03:59 +02:00
|
|
|
|
|
|
|
heap_freetuple(tup);
|
|
|
|
}
|
|
|
|
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
systable_endscan(scan);
|
|
|
|
|
2019-01-21 19:32:19 +01:00
|
|
|
table_close(depRel, RowExclusiveLock);
|
2005-08-01 06:03:59 +02:00
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2019-04-12 08:36:05 +02:00
|
|
|
/*
|
|
|
|
* 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))))
|
|
|
|
{
|
2020-09-04 20:32:10 +02:00
|
|
|
Form_pg_depend depform;
|
2019-04-12 08:36:05 +02:00
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2019-03-29 08:25:20 +01:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
|
2021-07-15 17:41:47 +02:00
|
|
|
if (isObjectPinned(&objAddr))
|
2019-03-29 08:25:20 +01:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("cannot remove dependency on %s because it is a system object",
|
2020-07-15 02:03:10 +02:00
|
|
|
getObjectDescription(&objAddr, false))));
|
2019-03-29 08:25:20 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* We can handle adding a dependency on something pinned, though, since
|
|
|
|
* that just means deleting the dependency entry.
|
|
|
|
*/
|
|
|
|
objAddr.objectId = newRefObjectId;
|
|
|
|
|
2021-07-15 17:41:47 +02:00
|
|
|
newIsPinned = isObjectPinned(&objAddr);
|
2019-03-29 08:25:20 +01:00
|
|
|
|
|
|
|
/* 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
|
|
|
|
{
|
2020-09-04 20:32:10 +02:00
|
|
|
Form_pg_depend depform;
|
|
|
|
|
2019-03-29 08:25:20 +01:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2007-12-02 00:44:44 +01:00
|
|
|
/*
|
|
|
|
* 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
|
2021-07-15 17:41:47 +02:00
|
|
|
isObjectPinned(const ObjectAddress *object)
|
2007-12-02 00:44:44 +01:00
|
|
|
{
|
2021-07-15 17:41:47 +02:00
|
|
|
return IsPinnedObject(object->classId, object->objectId);
|
2007-12-02 00:44:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Various special-purpose lookups and manipulations of pg_depend.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2011-02-08 22:08:41 +01:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
|
2019-01-21 19:32:19 +01:00
|
|
|
depRel = table_open(DependRelationId, AccessShareLock);
|
2011-02-08 22:08:41 +01:00
|
|
|
|
|
|
|
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,
|
Use an MVCC snapshot, rather than SnapshotNow, for catalog scans.
SnapshotNow scans have the undesirable property that, in the face of
concurrent updates, the scan can fail to see either the old or the new
versions of the row. In many cases, we work around this by requiring
DDL operations to hold AccessExclusiveLock on the object being
modified; in some cases, the existing locking is inadequate and random
failures occur as a result. This commit doesn't change anything
related to locking, but will hopefully pave the way to allowing lock
strength reductions in the future.
The major issue has held us back from making this change in the past
is that taking an MVCC snapshot is significantly more expensive than
using a static special snapshot such as SnapshotNow. However, testing
of various worst-case scenarios reveals that this problem is not
severe except under fairly extreme workloads. To mitigate those
problems, we avoid retaking the MVCC snapshot for each new scan;
instead, we take a new snapshot only when invalidation messages have
been processed. The catcache machinery already requires that
invalidation messages be sent before releasing the related heavyweight
lock; else other backends might rely on locally-cached data rather
than scanning the catalog at all. Thus, making snapshot reuse
dependent on the same guarantees shouldn't break anything that wasn't
already subtly broken.
Patch by me. Review by Michael Paquier and Andres Freund.
2013-07-02 15:47:01 +02:00
|
|
|
NULL, 2, key);
|
2011-02-08 22:08:41 +01:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2019-01-21 19:32:19 +01:00
|
|
|
table_close(depRel, AccessShareLock);
|
2011-02-08 22:08:41 +01:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-03-11 15:04:59 +01:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2006-08-21 02:57:26 +02:00
|
|
|
/*
|
|
|
|
* Detect whether a sequence is marked as "owned" by a column
|
|
|
|
*
|
2017-04-06 14:33:16 +02:00
|
|
|
* An ownership marker is an AUTO or INTERNAL dependency from the sequence to the
|
2006-08-21 02:57:26 +02:00
|
|
|
* column. If we find one, store the identity of the owning column
|
2017-08-16 06:22:32 +02:00
|
|
|
* into *tableId and *colId and return true; else return false.
|
2006-08-21 02:57:26 +02:00
|
|
|
*
|
|
|
|
* 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
|
2017-04-06 14:33:16 +02:00
|
|
|
sequenceIsOwned(Oid seqId, char deptype, Oid *tableId, int32 *colId)
|
2006-08-21 02:57:26 +02:00
|
|
|
{
|
|
|
|
bool ret = false;
|
|
|
|
Relation depRel;
|
|
|
|
ScanKeyData key[2];
|
|
|
|
SysScanDesc scan;
|
|
|
|
HeapTuple tup;
|
|
|
|
|
2019-01-21 19:32:19 +01:00
|
|
|
depRel = table_open(DependRelationId, AccessShareLock);
|
2006-08-21 02:57:26 +02:00
|
|
|
|
|
|
|
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,
|
Use an MVCC snapshot, rather than SnapshotNow, for catalog scans.
SnapshotNow scans have the undesirable property that, in the face of
concurrent updates, the scan can fail to see either the old or the new
versions of the row. In many cases, we work around this by requiring
DDL operations to hold AccessExclusiveLock on the object being
modified; in some cases, the existing locking is inadequate and random
failures occur as a result. This commit doesn't change anything
related to locking, but will hopefully pave the way to allowing lock
strength reductions in the future.
The major issue has held us back from making this change in the past
is that taking an MVCC snapshot is significantly more expensive than
using a static special snapshot such as SnapshotNow. However, testing
of various worst-case scenarios reveals that this problem is not
severe except under fairly extreme workloads. To mitigate those
problems, we avoid retaking the MVCC snapshot for each new scan;
instead, we take a new snapshot only when invalidation messages have
been processed. The catcache machinery already requires that
invalidation messages be sent before releasing the related heavyweight
lock; else other backends might rely on locally-cached data rather
than scanning the catalog at all. Thus, making snapshot reuse
dependent on the same guarantees shouldn't break anything that wasn't
already subtly broken.
Patch by me. Review by Michael Paquier and Andres Freund.
2013-07-02 15:47:01 +02:00
|
|
|
NULL, 2, key);
|
2006-08-21 02:57:26 +02:00
|
|
|
|
|
|
|
while (HeapTupleIsValid((tup = systable_getnext(scan))))
|
|
|
|
{
|
|
|
|
Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup);
|
|
|
|
|
|
|
|
if (depform->refclassid == RelationRelationId &&
|
2017-04-06 14:33:16 +02:00
|
|
|
depform->deptype == deptype)
|
2006-08-21 02:57:26 +02:00
|
|
|
{
|
|
|
|
*tableId = depform->refobjid;
|
|
|
|
*colId = depform->refobjsubid;
|
|
|
|
ret = true;
|
|
|
|
break; /* no need to keep scanning */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
systable_endscan(scan);
|
|
|
|
|
2019-01-21 19:32:19 +01:00
|
|
|
table_close(depRel, AccessShareLock);
|
2006-08-21 02:57:26 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2017-04-06 14:33:16 +02:00
|
|
|
* Collect a list of OIDs of all sequences owned by the specified relation,
|
2019-07-22 12:05:03 +02:00
|
|
|
* and column if specified. If deptype is not zero, then only find sequences
|
|
|
|
* with the specified dependency type.
|
2008-05-17 01:36:05 +02:00
|
|
|
*/
|
2019-07-22 12:05:03 +02:00
|
|
|
static List *
|
|
|
|
getOwnedSequences_internal(Oid relid, AttrNumber attnum, char deptype)
|
2008-05-17 01:36:05 +02:00
|
|
|
{
|
|
|
|
List *result = NIL;
|
|
|
|
Relation depRel;
|
2017-04-06 14:33:16 +02:00
|
|
|
ScanKeyData key[3];
|
2008-05-17 01:36:05 +02:00
|
|
|
SysScanDesc scan;
|
|
|
|
HeapTuple tup;
|
|
|
|
|
2019-01-21 19:32:19 +01:00
|
|
|
depRel = table_open(DependRelationId, AccessShareLock);
|
2008-05-17 01:36:05 +02:00
|
|
|
|
|
|
|
ScanKeyInit(&key[0],
|
|
|
|
Anum_pg_depend_refclassid,
|
|
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
|
|
ObjectIdGetDatum(RelationRelationId));
|
|
|
|
ScanKeyInit(&key[1],
|
|
|
|
Anum_pg_depend_refobjid,
|
|
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
|
|
ObjectIdGetDatum(relid));
|
2017-04-06 14:33:16 +02:00
|
|
|
if (attnum)
|
|
|
|
ScanKeyInit(&key[2],
|
|
|
|
Anum_pg_depend_refobjsubid,
|
|
|
|
BTEqualStrategyNumber, F_INT4EQ,
|
|
|
|
Int32GetDatum(attnum));
|
2008-05-17 01:36:05 +02:00
|
|
|
|
|
|
|
scan = systable_beginscan(depRel, DependReferenceIndexId, true,
|
2017-04-06 14:33:16 +02:00
|
|
|
NULL, attnum ? 3 : 2, key);
|
2008-05-17 01:36:05 +02:00
|
|
|
|
|
|
|
while (HeapTupleIsValid(tup = systable_getnext(scan)))
|
|
|
|
{
|
|
|
|
Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup);
|
|
|
|
|
|
|
|
/*
|
2017-04-06 14:33:16 +02:00
|
|
|
* We assume any auto or internal dependency of a sequence on a column
|
2008-05-17 01:36:05 +02:00
|
|
|
* 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 &&
|
2017-04-06 14:33:16 +02:00
|
|
|
(deprec->deptype == DEPENDENCY_AUTO || deprec->deptype == DEPENDENCY_INTERNAL) &&
|
2008-05-17 01:36:05 +02:00
|
|
|
get_rel_relkind(deprec->objid) == RELKIND_SEQUENCE)
|
|
|
|
{
|
2019-07-22 12:05:03 +02:00
|
|
|
if (!deptype || deprec->deptype == deptype)
|
|
|
|
result = lappend_oid(result, deprec->objid);
|
2008-05-17 01:36:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
systable_endscan(scan);
|
|
|
|
|
2019-01-21 19:32:19 +01:00
|
|
|
table_close(depRel, AccessShareLock);
|
2008-05-17 01:36:05 +02:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-04-06 14:33:16 +02:00
|
|
|
/*
|
2019-07-22 12:05:03 +02:00
|
|
|
* 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.
|
2017-04-06 14:33:16 +02:00
|
|
|
*/
|
|
|
|
Oid
|
2019-07-22 12:05:03 +02:00
|
|
|
getIdentitySequence(Oid relid, AttrNumber attnum, bool missing_ok)
|
2017-04-06 14:33:16 +02:00
|
|
|
{
|
2019-07-22 12:05:03 +02:00
|
|
|
List *seqlist = getOwnedSequences_internal(relid, attnum, DEPENDENCY_INTERNAL);
|
2017-04-06 14:33:16 +02:00
|
|
|
|
|
|
|
if (list_length(seqlist) > 1)
|
|
|
|
elog(ERROR, "more than one owned sequence found");
|
|
|
|
else if (list_length(seqlist) < 1)
|
2019-07-22 12:05:03 +02:00
|
|
|
{
|
|
|
|
if (missing_ok)
|
|
|
|
return InvalidOid;
|
|
|
|
else
|
|
|
|
elog(ERROR, "no owned sequence found");
|
|
|
|
}
|
2017-04-07 23:37:12 +02:00
|
|
|
|
|
|
|
return linitial_oid(seqlist);
|
2017-04-06 14:33:16 +02:00
|
|
|
}
|
2007-12-02 00:44:44 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* get_index_constraint
|
Fix ALTER COLUMN TYPE failure with a partial exclusion constraint.
ATExecAlterColumnType failed to consider the possibility that an index
that needs to be rebuilt might be a child of a constraint that needs to be
rebuilt. We missed this so far because usually a constraint index doesn't
have a direct dependency on its table, just on the constraint object.
But if there's a WHERE clause, then dependency analysis of the WHERE
clause results in direct dependencies on the column(s) mentioned in WHERE.
This led to trying to drop and rebuild both the constraint and its
underlying index.
In v11/HEAD, we successfully drop both the index and the constraint,
and then try to rebuild both, and of course the second rebuild hits a
duplicate-index-name problem. Before v11, it fails with obscure messages
about a missing relation OID, due to trying to drop the index twice.
This is essentially the same kind of problem noted in commit
20bef2c31: the possible dependency linkages are broader than what
ATExecAlterColumnType was designed for. It was probably OK when
written, but it's certainly been broken since the introduction of
partial exclusion constraints. Fix by adding an explicit check
for whether any of the indexes-to-be-rebuilt belong to any of the
constraints-to-be-rebuilt, and ignoring any that do.
In passing, fix a latent bug introduced by commit 8b08f7d48: in
get_constraint_index() we must "continue" not "break" when rejecting
a relation of a wrong relkind. This is harmless today because we don't
expect that code path to be taken anyway; but if there ever were any
relations to be ignored, the existing coding would have an extremely
undesirable dependency on the order of pg_depend entries.
Also adjust a couple of obsolete comments.
Per bug #15835 from Yaroslav Schekin. Back-patch to all supported
branches.
Discussion: https://postgr.es/m/15835-32d9b7a76c06a7a9@postgresql.org
2019-06-12 18:29:24 +02:00
|
|
|
* 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.
|
2007-12-02 00:44:44 +01:00
|
|
|
*/
|
|
|
|
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 */
|
2019-01-21 19:32:19 +01:00
|
|
|
depRel = table_open(DependRelationId, AccessShareLock);
|
2007-12-02 00:44:44 +01:00
|
|
|
|
|
|
|
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,
|
Use an MVCC snapshot, rather than SnapshotNow, for catalog scans.
SnapshotNow scans have the undesirable property that, in the face of
concurrent updates, the scan can fail to see either the old or the new
versions of the row. In many cases, we work around this by requiring
DDL operations to hold AccessExclusiveLock on the object being
modified; in some cases, the existing locking is inadequate and random
failures occur as a result. This commit doesn't change anything
related to locking, but will hopefully pave the way to allowing lock
strength reductions in the future.
The major issue has held us back from making this change in the past
is that taking an MVCC snapshot is significantly more expensive than
using a static special snapshot such as SnapshotNow. However, testing
of various worst-case scenarios reveals that this problem is not
severe except under fairly extreme workloads. To mitigate those
problems, we avoid retaking the MVCC snapshot for each new scan;
instead, we take a new snapshot only when invalidation messages have
been processed. The catcache machinery already requires that
invalidation messages be sent before releasing the related heavyweight
lock; else other backends might rely on locally-cached data rather
than scanning the catalog at all. Thus, making snapshot reuse
dependent on the same guarantees shouldn't break anything that wasn't
already subtly broken.
Patch by me. Review by Michael Paquier and Andres Freund.
2013-07-02 15:47:01 +02:00
|
|
|
NULL, 3, key);
|
2007-12-02 00:44:44 +01:00
|
|
|
|
|
|
|
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);
|
2019-01-21 19:32:19 +01:00
|
|
|
table_close(depRel, AccessShareLock);
|
2007-12-02 00:44:44 +01:00
|
|
|
|
|
|
|
return constraintId;
|
2002-07-12 20:43:19 +02:00
|
|
|
}
|
2019-03-29 08:25:20 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|