postgresql/src/backend/catalog/pg_shdepend.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1651 lines
46 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* pg_shdepend.c
* routines to support manipulation of the pg_shdepend relation
*
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
2010-09-20 22:08:53 +02:00
* src/backend/catalog/pg_shdepend.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
#include "catalog/pg_default_acl.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_extension.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_language.h"
#include "catalog/pg_largeobject.h"
#include "catalog/pg_largeobject_metadata.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_shdepend.h"
Implement multivariate n-distinct coefficients Add support for explicitly declared statistic objects (CREATE STATISTICS), allowing collection of statistics on more complex combinations that individual table columns. Companion commands DROP STATISTICS and ALTER STATISTICS ... OWNER TO / SET SCHEMA / RENAME are added too. All this DDL has been designed so that more statistic types can be added later on, such as multivariate most-common-values and multivariate histograms between columns of a single table, leaving room for permitting columns on multiple tables, too, as well as expressions. This commit only adds support for collection of n-distinct coefficient on user-specified sets of columns in a single table. This is useful to estimate number of distinct groups in GROUP BY and DISTINCT clauses; estimation errors there can cause over-allocation of memory in hashed aggregates, for instance, so it's a worthwhile problem to solve. A new special pseudo-type pg_ndistinct is used. (num-distinct estimation was deemed sufficiently useful by itself that this is worthwhile even if no further statistic types are added immediately; so much so that another version of essentially the same functionality was submitted by Kyotaro Horiguchi: https://postgr.es/m/20150828.173334.114731693.horiguchi.kyotaro@lab.ntt.co.jp though this commit does not use that code.) Author: Tomas Vondra. Some code rework by Álvaro. Reviewed-by: Dean Rasheed, David Rowley, Kyotaro Horiguchi, Jeff Janes, Ideriha Takeshi Discussion: https://postgr.es/m/543AFA15.4080608@fuzzy.cz https://postgr.es/m/20170320190220.ixlaueanxegqd5gr@alvherre.pgsql
2017-03-24 18:06:10 +01:00
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
#include "commands/alter.h"
#include "commands/collationcmds.h"
#include "commands/conversioncmds.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
#include "commands/publicationcmds.h"
#include "commands/schemacmds.h"
#include "commands/subscriptioncmds.h"
#include "commands/tablecmds.h"
Prevent drop of tablespaces used by partitioned relations When a tablespace is used in a partitioned relation (per commits ca4103025dfe in pg12 for tables and 33e6c34c3267 in pg11 for indexes), it is possible to drop the tablespace, potentially causing various problems. One such was reported in bug #16577, where a rewriting ALTER TABLE causes a server crash. Protect against this by using pg_shdepend to keep track of tablespaces when used for relations that don't keep physical files; we now abort a tablespace if we see that the tablespace is referenced from any partitioned relations. Backpatch this to 11, where this problem has been latent all along. We don't try to create pg_shdepend entries for existing partitioned indexes/tables, but any ones that are modified going forward will be protected. Note slight behavior change: when trying to drop a tablespace that contains both regular tables as well as partitioned ones, you'd previously get ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE and now you'll get ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST. Arguably, the latter is more correct. It is possible to add protecting pg_shdepend entries for existing tables/indexes, by doing ALTER TABLE ONLY some_partitioned_table SET TABLESPACE pg_default; ALTER TABLE ONLY some_partitioned_table SET TABLESPACE original_tablespace; for each partitioned table/index that is not in the database default tablespace. Because these partitioned objects do not have storage, no file needs to be actually moved, so it shouldn't take more time than what's required to acquire locks. This query can be used to search for such relations: SELECT ... FROM pg_class WHERE relkind IN ('p', 'I') AND reltablespace <> 0 Reported-by: Alexander Lakhin <exclusion@gmail.com> Discussion: https://postgr.es/m/16577-881633a9f9894fd5@postgresql.org Author: Álvaro Herrera <alvherre@alvh.no-ip.org> Reviewed-by: Michael Paquier <michael@paquier.xyz>
2021-01-14 19:32:14 +01:00
#include "commands/tablespace.h"
#include "commands/typecmds.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
typedef enum
{
LOCAL_OBJECT,
SHARED_OBJECT,
REMOTE_OBJECT
} SharedDependencyObjectType;
typedef struct
{
ObjectAddress object;
char deptype;
SharedDependencyObjectType objtype;
} ShDependObjectInfo;
static void getOidListDiff(Oid *list1, int *nlist1, Oid *list2, int *nlist2);
static Oid classIdGetDbId(Oid classId);
static void shdepChangeDep(Relation sdepRel,
Oid classid, Oid objid, int32 objsubid,
Oid refclassid, Oid refobjid,
SharedDependencyType deptype);
static void shdepAddDependency(Relation sdepRel,
Oid classId, Oid objectId, int32 objsubId,
Oid refclassId, Oid refobjId,
SharedDependencyType deptype);
static void shdepDropDependency(Relation sdepRel,
Oid classId, Oid objectId, int32 objsubId,
bool drop_subobjects,
Oid refclassId, Oid refobjId,
SharedDependencyType deptype);
static void storeObjectDescription(StringInfo descs,
SharedDependencyObjectType type,
ObjectAddress *object,
SharedDependencyType deptype,
int count);
/*
* recordSharedDependencyOn
*
* Record a dependency between 2 objects via their respective ObjectAddresses.
* The first argument is the dependent object, the second the one it
* references (which must be a shared object).
*
* This locks the referenced object and makes sure it still exists.
* Then it creates an entry in pg_shdepend. The lock is kept until
* the end of the transaction.
*
* Dependencies on pinned objects are not recorded.
*/
void
recordSharedDependencyOn(ObjectAddress *depender,
ObjectAddress *referenced,
SharedDependencyType deptype)
{
Relation sdepRel;
/*
* Objects in pg_shdepend can't have SubIds.
*/
Assert(depender->objectSubId == 0);
Assert(referenced->objectSubId == 0);
/*
* During bootstrap, do nothing since pg_shdepend may not exist yet.
* initdb will fill in appropriate pg_shdepend entries after bootstrap.
*/
if (IsBootstrapProcessingMode())
return;
sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
/* If the referenced object is pinned, do nothing. */
if (!IsPinnedObject(referenced->classId, referenced->objectId))
{
shdepAddDependency(sdepRel, depender->classId, depender->objectId,
depender->objectSubId,
referenced->classId, referenced->objectId,
deptype);
}
table_close(sdepRel, RowExclusiveLock);
}
/*
* recordDependencyOnOwner
*
* A convenient wrapper of recordSharedDependencyOn -- register the specified
* user as owner of the given object.
*
* Note: it's the caller's responsibility to ensure that there isn't an owner
* entry for the object already.
*/
void
recordDependencyOnOwner(Oid classId, Oid objectId, Oid owner)
{
ObjectAddress myself,
referenced;
myself.classId = classId;
myself.objectId = objectId;
myself.objectSubId = 0;
referenced.classId = AuthIdRelationId;
referenced.objectId = owner;
referenced.objectSubId = 0;
recordSharedDependencyOn(&myself, &referenced, SHARED_DEPENDENCY_OWNER);
}
/*
* shdepChangeDep
*
* Update shared dependency records to account for an updated referenced
* object. This is an internal workhorse for operations such as changing
* an object's owner.
*
* There must be no more than one existing entry for the given dependent
* object and dependency type! So in practice this can only be used for
Prevent drop of tablespaces used by partitioned relations When a tablespace is used in a partitioned relation (per commits ca4103025dfe in pg12 for tables and 33e6c34c3267 in pg11 for indexes), it is possible to drop the tablespace, potentially causing various problems. One such was reported in bug #16577, where a rewriting ALTER TABLE causes a server crash. Protect against this by using pg_shdepend to keep track of tablespaces when used for relations that don't keep physical files; we now abort a tablespace if we see that the tablespace is referenced from any partitioned relations. Backpatch this to 11, where this problem has been latent all along. We don't try to create pg_shdepend entries for existing partitioned indexes/tables, but any ones that are modified going forward will be protected. Note slight behavior change: when trying to drop a tablespace that contains both regular tables as well as partitioned ones, you'd previously get ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE and now you'll get ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST. Arguably, the latter is more correct. It is possible to add protecting pg_shdepend entries for existing tables/indexes, by doing ALTER TABLE ONLY some_partitioned_table SET TABLESPACE pg_default; ALTER TABLE ONLY some_partitioned_table SET TABLESPACE original_tablespace; for each partitioned table/index that is not in the database default tablespace. Because these partitioned objects do not have storage, no file needs to be actually moved, so it shouldn't take more time than what's required to acquire locks. This query can be used to search for such relations: SELECT ... FROM pg_class WHERE relkind IN ('p', 'I') AND reltablespace <> 0 Reported-by: Alexander Lakhin <exclusion@gmail.com> Discussion: https://postgr.es/m/16577-881633a9f9894fd5@postgresql.org Author: Álvaro Herrera <alvherre@alvh.no-ip.org> Reviewed-by: Michael Paquier <michael@paquier.xyz>
2021-01-14 19:32:14 +01:00
* updating SHARED_DEPENDENCY_OWNER and SHARED_DEPENDENCY_TABLESPACE
* entries, which should have that property.
*
* If there is no previous entry, we assume it was referencing a PINned
* object, so we create a new entry. If the new referenced object is
* PINned, we don't create an entry (and drop the old one, if any).
Prevent drop of tablespaces used by partitioned relations When a tablespace is used in a partitioned relation (per commits ca4103025dfe in pg12 for tables and 33e6c34c3267 in pg11 for indexes), it is possible to drop the tablespace, potentially causing various problems. One such was reported in bug #16577, where a rewriting ALTER TABLE causes a server crash. Protect against this by using pg_shdepend to keep track of tablespaces when used for relations that don't keep physical files; we now abort a tablespace if we see that the tablespace is referenced from any partitioned relations. Backpatch this to 11, where this problem has been latent all along. We don't try to create pg_shdepend entries for existing partitioned indexes/tables, but any ones that are modified going forward will be protected. Note slight behavior change: when trying to drop a tablespace that contains both regular tables as well as partitioned ones, you'd previously get ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE and now you'll get ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST. Arguably, the latter is more correct. It is possible to add protecting pg_shdepend entries for existing tables/indexes, by doing ALTER TABLE ONLY some_partitioned_table SET TABLESPACE pg_default; ALTER TABLE ONLY some_partitioned_table SET TABLESPACE original_tablespace; for each partitioned table/index that is not in the database default tablespace. Because these partitioned objects do not have storage, no file needs to be actually moved, so it shouldn't take more time than what's required to acquire locks. This query can be used to search for such relations: SELECT ... FROM pg_class WHERE relkind IN ('p', 'I') AND reltablespace <> 0 Reported-by: Alexander Lakhin <exclusion@gmail.com> Discussion: https://postgr.es/m/16577-881633a9f9894fd5@postgresql.org Author: Álvaro Herrera <alvherre@alvh.no-ip.org> Reviewed-by: Michael Paquier <michael@paquier.xyz>
2021-01-14 19:32:14 +01:00
* (For tablespaces, we don't record dependencies in certain cases, so
* there are other possible reasons for entries to be missing.)
*
* sdepRel must be the pg_shdepend relation, already opened and suitably
* locked.
*/
static void
shdepChangeDep(Relation sdepRel,
Oid classid, Oid objid, int32 objsubid,
Oid refclassid, Oid refobjid,
SharedDependencyType deptype)
{
Oid dbid = classIdGetDbId(classid);
HeapTuple oldtup = NULL;
HeapTuple scantup;
ScanKeyData key[4];
SysScanDesc scan;
/*
* Make sure the new referenced object doesn't go away while we record the
* dependency.
*/
shdepLockAndCheckObject(refclassid, refobjid);
/*
* Look for a previous entry
*/
ScanKeyInit(&key[0],
Anum_pg_shdepend_dbid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(dbid));
ScanKeyInit(&key[1],
Anum_pg_shdepend_classid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(classid));
ScanKeyInit(&key[2],
Anum_pg_shdepend_objid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(objid));
ScanKeyInit(&key[3],
Anum_pg_shdepend_objsubid,
BTEqualStrategyNumber, F_INT4EQ,
Int32GetDatum(objsubid));
scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
NULL, 4, key);
2005-10-15 04:49:52 +02:00
while ((scantup = systable_getnext(scan)) != NULL)
{
/* Ignore if not of the target dependency type */
if (((Form_pg_shdepend) GETSTRUCT(scantup))->deptype != deptype)
continue;
/* Caller screwed up if multiple matches */
if (oldtup)
elog(ERROR,
"multiple pg_shdepend entries for object %u/%u/%d deptype %c",
classid, objid, objsubid, deptype);
oldtup = heap_copytuple(scantup);
}
systable_endscan(scan);
if (IsPinnedObject(refclassid, refobjid))
{
/* No new entry needed, so just delete existing entry if any */
if (oldtup)
CatalogTupleDelete(sdepRel, &oldtup->t_self);
}
else if (oldtup)
{
/* Need to update existing entry */
Form_pg_shdepend shForm = (Form_pg_shdepend) GETSTRUCT(oldtup);
/* Since oldtup is a copy, we can just modify it in-memory */
shForm->refclassid = refclassid;
shForm->refobjid = refobjid;
CatalogTupleUpdate(sdepRel, &oldtup->t_self, oldtup);
}
else
{
/* Need to insert new entry */
Datum values[Natts_pg_shdepend];
bool nulls[Natts_pg_shdepend];
memset(nulls, false, sizeof(nulls));
values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(dbid);
values[Anum_pg_shdepend_classid - 1] = ObjectIdGetDatum(classid);
values[Anum_pg_shdepend_objid - 1] = ObjectIdGetDatum(objid);
values[Anum_pg_shdepend_objsubid - 1] = Int32GetDatum(objsubid);
values[Anum_pg_shdepend_refclassid - 1] = ObjectIdGetDatum(refclassid);
values[Anum_pg_shdepend_refobjid - 1] = ObjectIdGetDatum(refobjid);
values[Anum_pg_shdepend_deptype - 1] = CharGetDatum(deptype);
/*
* we are reusing oldtup just to avoid declaring a new variable, but
* it's certainly a new tuple
*/
oldtup = heap_form_tuple(RelationGetDescr(sdepRel), values, nulls);
CatalogTupleInsert(sdepRel, oldtup);
}
if (oldtup)
heap_freetuple(oldtup);
}
/*
* changeDependencyOnOwner
*
* Update the shared dependencies to account for the new owner.
*
* Note: we don't need an objsubid argument because only whole objects
* have owners.
*/
void
changeDependencyOnOwner(Oid classId, Oid objectId, Oid newOwnerId)
{
Relation sdepRel;
sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
/* Adjust the SHARED_DEPENDENCY_OWNER entry */
shdepChangeDep(sdepRel,
classId, objectId, 0,
AuthIdRelationId, newOwnerId,
SHARED_DEPENDENCY_OWNER);
/*----------
* There should never be a SHARED_DEPENDENCY_ACL entry for the owner,
* so get rid of it if there is one. This can happen if the new owner
* was previously granted some rights to the object.
*
* This step is analogous to aclnewowner's removal of duplicate entries
* in the ACL. We have to do it to handle this scenario:
* A grants some rights on an object to B
* ALTER OWNER changes the object's owner to B
* ALTER OWNER changes the object's owner to C
* The third step would remove all mention of B from the object's ACL,
* but we'd still have a SHARED_DEPENDENCY_ACL for B if we did not do
* things this way.
*
* The rule against having a SHARED_DEPENDENCY_ACL entry for the owner
* allows us to fix things up in just this one place, without having
* to make the various ALTER OWNER routines each know about it.
*----------
*/
shdepDropDependency(sdepRel, classId, objectId, 0, true,
AuthIdRelationId, newOwnerId,
SHARED_DEPENDENCY_ACL);
table_close(sdepRel, RowExclusiveLock);
}
Prevent drop of tablespaces used by partitioned relations When a tablespace is used in a partitioned relation (per commits ca4103025dfe in pg12 for tables and 33e6c34c3267 in pg11 for indexes), it is possible to drop the tablespace, potentially causing various problems. One such was reported in bug #16577, where a rewriting ALTER TABLE causes a server crash. Protect against this by using pg_shdepend to keep track of tablespaces when used for relations that don't keep physical files; we now abort a tablespace if we see that the tablespace is referenced from any partitioned relations. Backpatch this to 11, where this problem has been latent all along. We don't try to create pg_shdepend entries for existing partitioned indexes/tables, but any ones that are modified going forward will be protected. Note slight behavior change: when trying to drop a tablespace that contains both regular tables as well as partitioned ones, you'd previously get ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE and now you'll get ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST. Arguably, the latter is more correct. It is possible to add protecting pg_shdepend entries for existing tables/indexes, by doing ALTER TABLE ONLY some_partitioned_table SET TABLESPACE pg_default; ALTER TABLE ONLY some_partitioned_table SET TABLESPACE original_tablespace; for each partitioned table/index that is not in the database default tablespace. Because these partitioned objects do not have storage, no file needs to be actually moved, so it shouldn't take more time than what's required to acquire locks. This query can be used to search for such relations: SELECT ... FROM pg_class WHERE relkind IN ('p', 'I') AND reltablespace <> 0 Reported-by: Alexander Lakhin <exclusion@gmail.com> Discussion: https://postgr.es/m/16577-881633a9f9894fd5@postgresql.org Author: Álvaro Herrera <alvherre@alvh.no-ip.org> Reviewed-by: Michael Paquier <michael@paquier.xyz>
2021-01-14 19:32:14 +01:00
/*
* recordDependencyOnTablespace
*
* A convenient wrapper of recordSharedDependencyOn -- register the specified
* tablespace as default for the given object.
*
* Note: it's the caller's responsibility to ensure that there isn't a
* tablespace entry for the object already.
*/
void
recordDependencyOnTablespace(Oid classId, Oid objectId, Oid tablespace)
{
ObjectAddress myself,
referenced;
ObjectAddressSet(myself, classId, objectId);
ObjectAddressSet(referenced, TableSpaceRelationId, tablespace);
recordSharedDependencyOn(&myself, &referenced,
SHARED_DEPENDENCY_TABLESPACE);
}
/*
* changeDependencyOnTablespace
*
* Update the shared dependencies to account for the new tablespace.
*
* Note: we don't need an objsubid argument because only whole objects
* have tablespaces.
*/
void
changeDependencyOnTablespace(Oid classId, Oid objectId, Oid newTablespaceId)
{
Relation sdepRel;
sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
if (newTablespaceId != DEFAULTTABLESPACE_OID &&
newTablespaceId != InvalidOid)
shdepChangeDep(sdepRel,
classId, objectId, 0,
TableSpaceRelationId, newTablespaceId,
SHARED_DEPENDENCY_TABLESPACE);
else
shdepDropDependency(sdepRel,
classId, objectId, 0, true,
InvalidOid, InvalidOid,
SHARED_DEPENDENCY_INVALID);
table_close(sdepRel, RowExclusiveLock);
}
/*
* getOidListDiff
* Helper for updateAclDependencies.
*
* Takes two Oid arrays and removes elements that are common to both arrays,
* leaving just those that are in one input but not the other.
* We assume both arrays have been sorted and de-duped.
*/
static void
getOidListDiff(Oid *list1, int *nlist1, Oid *list2, int *nlist2)
{
int in1,
in2,
out1,
out2;
in1 = in2 = out1 = out2 = 0;
while (in1 < *nlist1 && in2 < *nlist2)
{
if (list1[in1] == list2[in2])
{
/* skip over duplicates */
in1++;
in2++;
}
else if (list1[in1] < list2[in2])
{
/* list1[in1] is not in list2 */
list1[out1++] = list1[in1++];
}
else
{
/* list2[in2] is not in list1 */
list2[out2++] = list2[in2++];
}
}
/* any remaining list1 entries are not in list2 */
while (in1 < *nlist1)
{
list1[out1++] = list1[in1++];
}
/* any remaining list2 entries are not in list1 */
while (in2 < *nlist2)
{
list2[out2++] = list2[in2++];
}
*nlist1 = out1;
*nlist2 = out2;
}
/*
* updateAclDependencies
* Update the pg_shdepend info for an object's ACL during GRANT/REVOKE.
*
* classId, objectId, objsubId: identify the object whose ACL this is
* ownerId: role owning the object
* noldmembers, oldmembers: array of roleids appearing in old ACL
* nnewmembers, newmembers: array of roleids appearing in new ACL
*
* We calculate the differences between the new and old lists of roles,
* and then insert or delete from pg_shdepend as appropriate.
*
* Note that we can't just insert all referenced roles blindly during GRANT,
* because we would end up with duplicate registered dependencies. We could
* check for existence of the tuples before inserting, but that seems to be
* more expensive than what we are doing here. Likewise we can't just delete
* blindly during REVOKE, because the user may still have other privileges.
* It is also possible that REVOKE actually adds dependencies, due to
* instantiation of a formerly implicit default ACL (although at present,
* all such dependencies should be for the owning role, which we ignore here).
*
* NOTE: Both input arrays must be sorted and de-duped. (Typically they
* are extracted from an ACL array by aclmembers(), which takes care of
* both requirements.) The arrays are pfreed before return.
*/
void
updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,
Oid ownerId,
int noldmembers, Oid *oldmembers,
int nnewmembers, Oid *newmembers)
{
Relation sdepRel;
int i;
/*
* Remove entries that are common to both lists; those represent existing
* dependencies we don't need to change.
*
* OK to overwrite the inputs since we'll pfree them anyway.
*/
getOidListDiff(oldmembers, &noldmembers, newmembers, &nnewmembers);
if (noldmembers > 0 || nnewmembers > 0)
{
sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
/* Add new dependencies that weren't already present */
for (i = 0; i < nnewmembers; i++)
{
Oid roleid = newmembers[i];
/*
* Skip the owner: he has an OWNER shdep entry instead. (This is
* not just a space optimization; it makes ALTER OWNER easier. See
* notes in changeDependencyOnOwner.)
*/
if (roleid == ownerId)
continue;
/* Skip pinned roles; they don't need dependency entries */
if (IsPinnedObject(AuthIdRelationId, roleid))
continue;
shdepAddDependency(sdepRel, classId, objectId, objsubId,
AuthIdRelationId, roleid,
SHARED_DEPENDENCY_ACL);
}
/* Drop no-longer-used old dependencies */
for (i = 0; i < noldmembers; i++)
{
Oid roleid = oldmembers[i];
/* Skip the owner, same as above */
if (roleid == ownerId)
continue;
/* Skip pinned roles */
if (IsPinnedObject(AuthIdRelationId, roleid))
continue;
shdepDropDependency(sdepRel, classId, objectId, objsubId,
false, /* exact match on objsubId */
AuthIdRelationId, roleid,
SHARED_DEPENDENCY_ACL);
}
table_close(sdepRel, RowExclusiveLock);
}
if (oldmembers)
pfree(oldmembers);
if (newmembers)
pfree(newmembers);
}
/*
* A struct to keep track of dependencies found in other databases.
*/
typedef struct
{
Oid dbOid;
int count;
} remoteDep;
/*
* qsort comparator for ShDependObjectInfo items
*/
static int
shared_dependency_comparator(const void *a, const void *b)
{
const ShDependObjectInfo *obja = (const ShDependObjectInfo *) a;
const ShDependObjectInfo *objb = (const ShDependObjectInfo *) b;
/*
* Primary sort key is OID ascending.
*/
if (obja->object.objectId < objb->object.objectId)
return -1;
if (obja->object.objectId > objb->object.objectId)
return 1;
/*
* Next sort on catalog ID, in case identical OIDs appear in different
* catalogs. Sort direction is pretty arbitrary here.
*/
if (obja->object.classId < objb->object.classId)
return -1;
if (obja->object.classId > objb->object.classId)
return 1;
/*
* Sort on object subId.
*
* We sort the subId as an unsigned int so that 0 (the whole object) will
* come first.
*/
if ((unsigned int) obja->object.objectSubId < (unsigned int) objb->object.objectSubId)
return -1;
if ((unsigned int) obja->object.objectSubId > (unsigned int) objb->object.objectSubId)
return 1;
/*
* Last, sort on deptype, in case the same object has multiple dependency
* types. (Note that there's no need to consider objtype, as that's
* determined by the catalog OID.)
*/
if (obja->deptype < objb->deptype)
return -1;
if (obja->deptype > objb->deptype)
return 1;
return 0;
}
/*
* checkSharedDependencies
*
* Check whether there are shared dependency entries for a given shared
* object; return true if so.
*
* In addition, return a string containing a newline-separated list of object
* descriptions that depend on the shared object, or NULL if none is found.
* We actually return two such strings; the "detail" result is suitable for
* returning to the client as an errdetail() string, and is limited in size.
* The "detail_log" string is potentially much longer, and should be emitted
* to the server log only.
*
* We can find three different kinds of dependencies: dependencies on objects
* of the current database; dependencies on shared objects; and dependencies
* on objects local to other databases. We can (and do) provide descriptions
* of the two former kinds of objects, but we can't do that for "remote"
* objects, so we just provide a count of them.
*/
bool
checkSharedDependencies(Oid classId, Oid objectId,
char **detail_msg, char **detail_log_msg)
{
Relation sdepRel;
ScanKeyData key[2];
SysScanDesc scan;
HeapTuple tup;
int numReportedDeps = 0;
int numNotReportedDeps = 0;
int numNotReportedDbs = 0;
List *remDeps = NIL;
ListCell *cell;
ObjectAddress object;
ShDependObjectInfo *objects;
int numobjects;
int allocedobjects;
StringInfoData descs;
StringInfoData alldescs;
/* This case can be dispatched quickly */
if (IsPinnedObject(classId, objectId))
{
object.classId = classId;
object.objectId = objectId;
object.objectSubId = 0;
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("cannot drop %s because it is required by the database system",
getObjectDescription(&object, false))));
}
/*
* We limit the number of dependencies reported to the client to
* MAX_REPORTED_DEPS, since client software may not deal well with
* enormous error strings. The server log always gets a full report.
*
* For stability of regression test results, we sort local and shared
* objects by OID before reporting them. We don't worry about the order
* in which other databases are reported, though.
*/
#define MAX_REPORTED_DEPS 100
allocedobjects = 128; /* arbitrary initial array size */
objects = (ShDependObjectInfo *)
palloc(allocedobjects * sizeof(ShDependObjectInfo));
numobjects = 0;
initStringInfo(&descs);
initStringInfo(&alldescs);
sdepRel = table_open(SharedDependRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_shdepend_refclassid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(classId));
ScanKeyInit(&key[1],
Anum_pg_shdepend_refobjid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(objectId));
2005-10-15 04:49:52 +02:00
scan = systable_beginscan(sdepRel, SharedDependReferenceIndexId, true,
NULL, 2, key);
while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tup);
object.classId = sdepForm->classid;
object.objectId = sdepForm->objid;
object.objectSubId = sdepForm->objsubid;
/*
* If it's a dependency local to this database or it's a shared
* object, add it to the objects array.
*
* If it's a remote dependency, keep track of it so we can report the
* number of them later.
*/
if (sdepForm->dbid == MyDatabaseId ||
sdepForm->dbid == InvalidOid)
{
if (numobjects >= allocedobjects)
{
allocedobjects *= 2;
objects = (ShDependObjectInfo *)
repalloc(objects,
allocedobjects * sizeof(ShDependObjectInfo));
}
objects[numobjects].object = object;
objects[numobjects].deptype = sdepForm->deptype;
objects[numobjects].objtype = (sdepForm->dbid == MyDatabaseId) ?
LOCAL_OBJECT : SHARED_OBJECT;
numobjects++;
}
else
{
/* It's not local nor shared, so it must be remote. */
remoteDep *dep;
bool stored = false;
/*
* XXX this info is kept on a simple List. Maybe it's not good
* for performance, but using a hash table seems needlessly
* complex. The expected number of databases is not high anyway,
* I suppose.
*/
foreach(cell, remDeps)
{
dep = lfirst(cell);
if (dep->dbOid == sdepForm->dbid)
{
dep->count++;
stored = true;
break;
}
}
if (!stored)
{
dep = (remoteDep *) palloc(sizeof(remoteDep));
dep->dbOid = sdepForm->dbid;
dep->count = 1;
remDeps = lappend(remDeps, dep);
}
}
}
systable_endscan(scan);
table_close(sdepRel, AccessShareLock);
/*
* Sort and report local and shared objects.
*/
if (numobjects > 1)
qsort(objects, numobjects,
sizeof(ShDependObjectInfo), shared_dependency_comparator);
for (int i = 0; i < numobjects; i++)
{
if (numReportedDeps < MAX_REPORTED_DEPS)
{
numReportedDeps++;
storeObjectDescription(&descs,
objects[i].objtype,
&objects[i].object,
objects[i].deptype,
0);
}
else
numNotReportedDeps++;
storeObjectDescription(&alldescs,
objects[i].objtype,
&objects[i].object,
objects[i].deptype,
0);
}
/*
* Summarize dependencies in remote databases.
*/
foreach(cell, remDeps)
{
remoteDep *dep = lfirst(cell);
object.classId = DatabaseRelationId;
object.objectId = dep->dbOid;
object.objectSubId = 0;
if (numReportedDeps < MAX_REPORTED_DEPS)
{
numReportedDeps++;
storeObjectDescription(&descs, REMOTE_OBJECT, &object,
SHARED_DEPENDENCY_INVALID, dep->count);
}
else
numNotReportedDbs++;
storeObjectDescription(&alldescs, REMOTE_OBJECT, &object,
SHARED_DEPENDENCY_INVALID, dep->count);
}
pfree(objects);
list_free_deep(remDeps);
if (descs.len == 0)
{
pfree(descs.data);
pfree(alldescs.data);
*detail_msg = *detail_log_msg = NULL;
return false;
}
if (numNotReportedDeps > 0)
appendStringInfo(&descs, ngettext("\nand %d other object "
"(see server log for list)",
"\nand %d other objects "
"(see server log for list)",
numNotReportedDeps),
numNotReportedDeps);
if (numNotReportedDbs > 0)
appendStringInfo(&descs, ngettext("\nand objects in %d other database "
"(see server log for list)",
"\nand objects in %d other databases "
"(see server log for list)",
numNotReportedDbs),
numNotReportedDbs);
*detail_msg = descs.data;
*detail_log_msg = alldescs.data;
return true;
}
/*
* copyTemplateDependencies
*
* Routine to create the initial shared dependencies of a new database.
* We simply copy the dependencies from the template database.
*/
void
copyTemplateDependencies(Oid templateDbId, Oid newDbId)
{
Relation sdepRel;
TupleDesc sdepDesc;
ScanKeyData key[1];
SysScanDesc scan;
HeapTuple tup;
CatalogIndexState indstate;
TupleTableSlot **slot;
int max_slots,
slot_init_count,
slot_stored_count;
sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
sdepDesc = RelationGetDescr(sdepRel);
/*
* Allocate the slots to use, but delay costly initialization until we
* know that they will be used.
*/
max_slots = MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_shdepend);
slot = palloc(sizeof(TupleTableSlot *) * max_slots);
indstate = CatalogOpenIndexes(sdepRel);
/* Scan all entries with dbid = templateDbId */
ScanKeyInit(&key[0],
Anum_pg_shdepend_dbid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(templateDbId));
scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
NULL, 1, key);
/* number of slots currently storing tuples */
slot_stored_count = 0;
/* number of slots currently initialized */
slot_init_count = 0;
/*
* Copy the entries of the original database, changing the database Id to
* that of the new database. Note that because we are not copying rows
* with dbId == 0 (ie, rows describing dependent shared objects) we won't
* copy the ownership dependency of the template database itself; this is
* what we want.
*/
while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
Form_pg_shdepend shdep;
if (slot_init_count < max_slots)
{
slot[slot_stored_count] = MakeSingleTupleTableSlot(sdepDesc, &TTSOpsHeapTuple);
slot_init_count++;
}
ExecClearTuple(slot[slot_stored_count]);
memset(slot[slot_stored_count]->tts_isnull, false,
slot[slot_stored_count]->tts_tupleDescriptor->natts * sizeof(bool));
shdep = (Form_pg_shdepend) GETSTRUCT(tup);
slot[slot_stored_count]->tts_values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(newDbId);
slot[slot_stored_count]->tts_values[Anum_pg_shdepend_classid - 1] = shdep->classid;
slot[slot_stored_count]->tts_values[Anum_pg_shdepend_objid - 1] = shdep->objid;
slot[slot_stored_count]->tts_values[Anum_pg_shdepend_objsubid - 1] = shdep->objsubid;
slot[slot_stored_count]->tts_values[Anum_pg_shdepend_refclassid - 1] = shdep->refclassid;
slot[slot_stored_count]->tts_values[Anum_pg_shdepend_refobjid - 1] = shdep->refobjid;
slot[slot_stored_count]->tts_values[Anum_pg_shdepend_deptype - 1] = shdep->deptype;
ExecStoreVirtualTuple(slot[slot_stored_count]);
slot_stored_count++;
/* If slots are full, insert a batch of tuples */
if (slot_stored_count == max_slots)
{
CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slot_stored_count, indstate);
slot_stored_count = 0;
}
}
/* Insert any tuples left in the buffer */
if (slot_stored_count > 0)
CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slot_stored_count, indstate);
systable_endscan(scan);
CatalogCloseIndexes(indstate);
table_close(sdepRel, RowExclusiveLock);
/* Drop only the number of slots used */
for (int i = 0; i < slot_init_count; i++)
ExecDropSingleTupleTableSlot(slot[i]);
pfree(slot);
}
/*
* dropDatabaseDependencies
*
* Delete pg_shdepend entries corresponding to a database that's being
* dropped.
*/
void
dropDatabaseDependencies(Oid databaseId)
{
Relation sdepRel;
ScanKeyData key[1];
SysScanDesc scan;
HeapTuple tup;
sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
/*
* First, delete all the entries that have the database Oid in the dbid
* field.
*/
ScanKeyInit(&key[0],
Anum_pg_shdepend_dbid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(databaseId));
/* We leave the other index fields unspecified */
scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
NULL, 1, key);
while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
CatalogTupleDelete(sdepRel, &tup->t_self);
}
systable_endscan(scan);
/* Now delete all entries corresponding to the database itself */
shdepDropDependency(sdepRel, DatabaseRelationId, databaseId, 0, true,
InvalidOid, InvalidOid,
SHARED_DEPENDENCY_INVALID);
table_close(sdepRel, RowExclusiveLock);
}
/*
* deleteSharedDependencyRecordsFor
*
2005-08-30 03:07:54 +02:00
* Delete all pg_shdepend entries corresponding to an object that's being
* dropped or modified. The object is assumed to be either a shared object
* or local to the current database (the classId tells us which).
*
* If objectSubId is zero, we are deleting a whole object, so get rid of
* pg_shdepend entries for subobjects as well.
*/
void
deleteSharedDependencyRecordsFor(Oid classId, Oid objectId, int32 objectSubId)
{
Relation sdepRel;
sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
shdepDropDependency(sdepRel, classId, objectId, objectSubId,
(objectSubId == 0),
InvalidOid, InvalidOid,
SHARED_DEPENDENCY_INVALID);
table_close(sdepRel, RowExclusiveLock);
}
/*
* shdepAddDependency
* Internal workhorse for inserting into pg_shdepend
*
* sdepRel must be the pg_shdepend relation, already opened and suitably
* locked.
*/
static void
shdepAddDependency(Relation sdepRel,
Oid classId, Oid objectId, int32 objsubId,
Oid refclassId, Oid refobjId,
SharedDependencyType deptype)
{
HeapTuple tup;
Datum values[Natts_pg_shdepend];
bool nulls[Natts_pg_shdepend];
/*
* Make sure the object doesn't go away while we record the dependency on
* it. DROP routines should lock the object exclusively before they check
* shared dependencies.
*/
shdepLockAndCheckObject(refclassId, refobjId);
memset(nulls, false, sizeof(nulls));
/*
* Form the new tuple and record the dependency.
*/
values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(classIdGetDbId(classId));
values[Anum_pg_shdepend_classid - 1] = ObjectIdGetDatum(classId);
values[Anum_pg_shdepend_objid - 1] = ObjectIdGetDatum(objectId);
values[Anum_pg_shdepend_objsubid - 1] = Int32GetDatum(objsubId);
values[Anum_pg_shdepend_refclassid - 1] = ObjectIdGetDatum(refclassId);
values[Anum_pg_shdepend_refobjid - 1] = ObjectIdGetDatum(refobjId);
values[Anum_pg_shdepend_deptype - 1] = CharGetDatum(deptype);
tup = heap_form_tuple(sdepRel->rd_att, values, nulls);
CatalogTupleInsert(sdepRel, tup);
/* clean up */
heap_freetuple(tup);
}
/*
* shdepDropDependency
* Internal workhorse for deleting entries from pg_shdepend.
*
* We drop entries having the following properties:
* dependent object is the one identified by classId/objectId/objsubId
* if refclassId isn't InvalidOid, it must match the entry's refclassid
* if refobjId isn't InvalidOid, it must match the entry's refobjid
* if deptype isn't SHARED_DEPENDENCY_INVALID, it must match entry's deptype
*
* If drop_subobjects is true, we ignore objsubId and consider all entries
* matching classId/objectId.
*
* sdepRel must be the pg_shdepend relation, already opened and suitably
* locked.
*/
static void
shdepDropDependency(Relation sdepRel,
Oid classId, Oid objectId, int32 objsubId,
bool drop_subobjects,
Oid refclassId, Oid refobjId,
SharedDependencyType deptype)
{
ScanKeyData key[4];
int nkeys;
SysScanDesc scan;
HeapTuple tup;
/* Scan for entries matching the dependent object */
ScanKeyInit(&key[0],
Anum_pg_shdepend_dbid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(classIdGetDbId(classId)));
ScanKeyInit(&key[1],
Anum_pg_shdepend_classid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(classId));
ScanKeyInit(&key[2],
Anum_pg_shdepend_objid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(objectId));
if (drop_subobjects)
nkeys = 3;
else
{
ScanKeyInit(&key[3],
Anum_pg_shdepend_objsubid,
BTEqualStrategyNumber, F_INT4EQ,
Int32GetDatum(objsubId));
nkeys = 4;
}
scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
NULL, nkeys, key);
while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
Form_pg_shdepend shdepForm = (Form_pg_shdepend) GETSTRUCT(tup);
/* Filter entries according to additional parameters */
if (OidIsValid(refclassId) && shdepForm->refclassid != refclassId)
continue;
if (OidIsValid(refobjId) && shdepForm->refobjid != refobjId)
continue;
if (deptype != SHARED_DEPENDENCY_INVALID &&
shdepForm->deptype != deptype)
continue;
/* OK, delete it */
CatalogTupleDelete(sdepRel, &tup->t_self);
}
systable_endscan(scan);
}
/*
* classIdGetDbId
*
* Get the database Id that should be used in pg_shdepend, given the OID
* of the catalog containing the object. For shared objects, it's 0
* (InvalidOid); for all other objects, it's the current database Id.
*/
static Oid
classIdGetDbId(Oid classId)
{
Oid dbId;
if (IsSharedRelation(classId))
dbId = InvalidOid;
else
dbId = MyDatabaseId;
return dbId;
}
/*
* shdepLockAndCheckObject
*
* Lock the object that we are about to record a dependency on.
* After it's locked, verify that it hasn't been dropped while we
* weren't looking. If the object has been dropped, this function
* does not return!
*/
void
shdepLockAndCheckObject(Oid classId, Oid objectId)
{
/* AccessShareLock should be OK, since we are not modifying the object */
LockSharedObject(classId, objectId, 0, AccessShareLock);
switch (classId)
{
case AuthIdRelationId:
if (!SearchSysCacheExists1(AUTHOID, ObjectIdGetDatum(objectId)))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("role %u was concurrently dropped",
objectId)));
break;
case TableSpaceRelationId:
{
/* For lack of a syscache on pg_tablespace, do this: */
char *tablespace = get_tablespace_name(objectId);
2005-10-15 04:49:52 +02:00
if (tablespace == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("tablespace %u was concurrently dropped",
objectId)));
pfree(tablespace);
break;
}
case DatabaseRelationId:
{
/* For lack of a syscache on pg_database, do this: */
char *database = get_database_name(objectId);
if (database == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("database %u was concurrently dropped",
objectId)));
pfree(database);
break;
}
2010-02-26 03:01:40 +01:00
default:
elog(ERROR, "unrecognized shared classId: %u", classId);
}
}
/*
* storeObjectDescription
* Append the description of a dependent object to "descs"
*
* While searching for dependencies of a shared object, we stash the
* descriptions of dependent objects we find in a single string, which we
* later pass to ereport() in the DETAIL field when somebody attempts to
* drop a referenced shared object.
*
* When type is LOCAL_OBJECT or SHARED_OBJECT, we expect object to be the
* dependent object, deptype is the dependency type, and count is not used.
* When type is REMOTE_OBJECT, we expect object to be the database object,
* and count to be nonzero; deptype is not used in this case.
*/
static void
storeObjectDescription(StringInfo descs,
SharedDependencyObjectType type,
ObjectAddress *object,
SharedDependencyType deptype,
int count)
{
char *objdesc = getObjectDescription(object, false);
/*
* An object being dropped concurrently doesn't need to be reported.
*/
if (objdesc == NULL)
return;
/* separate entries with a newline */
if (descs->len != 0)
appendStringInfoChar(descs, '\n');
switch (type)
{
case LOCAL_OBJECT:
case SHARED_OBJECT:
if (deptype == SHARED_DEPENDENCY_OWNER)
appendStringInfo(descs, _("owner of %s"), objdesc);
else if (deptype == SHARED_DEPENDENCY_ACL)
appendStringInfo(descs, _("privileges for %s"), objdesc);
else if (deptype == SHARED_DEPENDENCY_POLICY)
appendStringInfo(descs, _("target of %s"), objdesc);
Prevent drop of tablespaces used by partitioned relations When a tablespace is used in a partitioned relation (per commits ca4103025dfe in pg12 for tables and 33e6c34c3267 in pg11 for indexes), it is possible to drop the tablespace, potentially causing various problems. One such was reported in bug #16577, where a rewriting ALTER TABLE causes a server crash. Protect against this by using pg_shdepend to keep track of tablespaces when used for relations that don't keep physical files; we now abort a tablespace if we see that the tablespace is referenced from any partitioned relations. Backpatch this to 11, where this problem has been latent all along. We don't try to create pg_shdepend entries for existing partitioned indexes/tables, but any ones that are modified going forward will be protected. Note slight behavior change: when trying to drop a tablespace that contains both regular tables as well as partitioned ones, you'd previously get ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE and now you'll get ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST. Arguably, the latter is more correct. It is possible to add protecting pg_shdepend entries for existing tables/indexes, by doing ALTER TABLE ONLY some_partitioned_table SET TABLESPACE pg_default; ALTER TABLE ONLY some_partitioned_table SET TABLESPACE original_tablespace; for each partitioned table/index that is not in the database default tablespace. Because these partitioned objects do not have storage, no file needs to be actually moved, so it shouldn't take more time than what's required to acquire locks. This query can be used to search for such relations: SELECT ... FROM pg_class WHERE relkind IN ('p', 'I') AND reltablespace <> 0 Reported-by: Alexander Lakhin <exclusion@gmail.com> Discussion: https://postgr.es/m/16577-881633a9f9894fd5@postgresql.org Author: Álvaro Herrera <alvherre@alvh.no-ip.org> Reviewed-by: Michael Paquier <michael@paquier.xyz>
2021-01-14 19:32:14 +01:00
else if (deptype == SHARED_DEPENDENCY_TABLESPACE)
appendStringInfo(descs, _("tablespace for %s"), objdesc);
else
elog(ERROR, "unrecognized dependency type: %d",
(int) deptype);
break;
case REMOTE_OBJECT:
/* translator: %s will always be "database %s" */
appendStringInfo(descs, ngettext("%d object in %s",
"%d objects in %s",
count),
count, objdesc);
break;
default:
elog(ERROR, "unrecognized object type: %d", type);
}
pfree(objdesc);
}
/*
* shdepDropOwned
*
* Drop the objects owned by any one of the given RoleIds. If a role has
* access to an object, the grant will be removed as well (but the object
* will not, of course).
*
* We can revoke grants immediately while doing the scan, but drops are
* saved up and done all at once with performMultipleDeletions. This
* is necessary so that we don't get failures from trying to delete
* interdependent objects in the wrong order.
*/
void
shdepDropOwned(List *roleids, DropBehavior behavior)
{
Relation sdepRel;
ListCell *cell;
ObjectAddresses *deleteobjs;
deleteobjs = new_object_addresses();
/*
* We don't need this strong a lock here, but we'll call routines that
* acquire RowExclusiveLock. Better get that right now to avoid potential
* deadlock failures.
*/
sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
/*
* For each role, find the dependent objects and drop them using the
* regular (non-shared) dependency management.
*/
foreach(cell, roleids)
{
Oid roleid = lfirst_oid(cell);
ScanKeyData key[2];
SysScanDesc scan;
HeapTuple tuple;
/* Doesn't work for pinned objects */
if (IsPinnedObject(AuthIdRelationId, roleid))
{
ObjectAddress obj;
obj.classId = AuthIdRelationId;
obj.objectId = roleid;
obj.objectSubId = 0;
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("cannot drop objects owned by %s because they are "
"required by the database system",
getObjectDescription(&obj, false))));
}
ScanKeyInit(&key[0],
Anum_pg_shdepend_refclassid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(AuthIdRelationId));
ScanKeyInit(&key[1],
Anum_pg_shdepend_refobjid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(roleid));
scan = systable_beginscan(sdepRel, SharedDependReferenceIndexId, true,
NULL, 2, key);
while ((tuple = systable_getnext(scan)) != NULL)
{
Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tuple);
ObjectAddress obj;
/*
* We only operate on shared objects and objects in the current
* database
*/
if (sdepForm->dbid != MyDatabaseId &&
sdepForm->dbid != InvalidOid)
continue;
switch (sdepForm->deptype)
{
/* Shouldn't happen */
case SHARED_DEPENDENCY_INVALID:
elog(ERROR, "unexpected dependency type");
break;
case SHARED_DEPENDENCY_POLICY:
/*
* Try to remove role from policy; if unable to, remove
* policy.
*/
if (!RemoveRoleFromObjectPolicy(roleid,
sdepForm->classid,
sdepForm->objid))
{
obj.classId = sdepForm->classid;
obj.objectId = sdepForm->objid;
obj.objectSubId = sdepForm->objsubid;
/*
* Acquire lock on object, then verify this dependency
* is still relevant. If not, the object might have
* been dropped or the policy modified. Ignore the
* object in that case.
*/
AcquireDeletionLock(&obj, 0);
if (!systable_recheck_tuple(scan, tuple))
{
ReleaseDeletionLock(&obj);
break;
}
add_exact_object_address(&obj, deleteobjs);
}
break;
case SHARED_DEPENDENCY_ACL:
/*
* Dependencies on role grants are recorded using
* SHARED_DEPENDENCY_ACL, but unlike a regular ACL list
* which stores all permissions for a particular object in
* a single ACL array, there's a separate catalog row for
* each grant - so removing the grant just means removing
* the entire row.
*/
if (sdepForm->classid != AuthMemRelationId)
{
RemoveRoleFromObjectACL(roleid,
sdepForm->classid,
sdepForm->objid);
break;
}
/* FALLTHROUGH */
case SHARED_DEPENDENCY_OWNER:
/*
* Save it for deletion below, if it's a local object or a
* role grant. Other shared objects, such as databases,
* should not be removed here.
*/
if (sdepForm->dbid == MyDatabaseId ||
sdepForm->classid == AuthMemRelationId)
{
obj.classId = sdepForm->classid;
obj.objectId = sdepForm->objid;
obj.objectSubId = sdepForm->objsubid;
/* as above */
AcquireDeletionLock(&obj, 0);
if (!systable_recheck_tuple(scan, tuple))
{
ReleaseDeletionLock(&obj);
break;
}
add_exact_object_address(&obj, deleteobjs);
}
break;
}
}
systable_endscan(scan);
}
/*
* For stability of deletion-report ordering, sort the objects into
* approximate reverse creation order before deletion. (This might also
* make the deletion go a bit faster, since there's less chance of having
* to rearrange the objects due to dependencies.)
*/
sort_object_addresses(deleteobjs);
/* the dependency mechanism does the actual work */
performMultipleDeletions(deleteobjs, behavior, 0);
table_close(sdepRel, RowExclusiveLock);
free_object_addresses(deleteobjs);
}
/*
* shdepReassignOwned
*
* Change the owner of objects owned by any of the roles in roleids to
* newrole. Grants are not touched.
*/
void
shdepReassignOwned(List *roleids, Oid newrole)
{
Relation sdepRel;
ListCell *cell;
/*
* We don't need this strong a lock here, but we'll call routines that
* acquire RowExclusiveLock. Better get that right now to avoid potential
* deadlock problems.
*/
sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
foreach(cell, roleids)
{
SysScanDesc scan;
ScanKeyData key[2];
HeapTuple tuple;
Oid roleid = lfirst_oid(cell);
/* Refuse to work on pinned roles */
if (IsPinnedObject(AuthIdRelationId, roleid))
{
ObjectAddress obj;
obj.classId = AuthIdRelationId;
obj.objectId = roleid;
obj.objectSubId = 0;
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
getObjectDescription(&obj, false))));
/*
* There's no need to tell the whole truth, which is that we
* didn't track these dependencies at all ...
*/
}
ScanKeyInit(&key[0],
Anum_pg_shdepend_refclassid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(AuthIdRelationId));
ScanKeyInit(&key[1],
Anum_pg_shdepend_refobjid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(roleid));
scan = systable_beginscan(sdepRel, SharedDependReferenceIndexId, true,
NULL, 2, key);
while ((tuple = systable_getnext(scan)) != NULL)
{
Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tuple);
MemoryContext cxt,
oldcxt;
/*
* We only operate on shared objects and objects in the current
* database
*/
if (sdepForm->dbid != MyDatabaseId &&
sdepForm->dbid != InvalidOid)
continue;
/* We leave non-owner dependencies alone */
if (sdepForm->deptype != SHARED_DEPENDENCY_OWNER)
continue;
/*
* The various ALTER OWNER routines tend to leak memory in
* CurrentMemoryContext. That's not a problem when they're only
* called once per command; but in this usage where we might be
* touching many objects, it can amount to a serious memory leak.
* Fix that by running each call in a short-lived context.
*/
cxt = AllocSetContextCreate(CurrentMemoryContext,
"shdepReassignOwned",
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(cxt);
/* Issue the appropriate ALTER OWNER call */
switch (sdepForm->classid)
{
case TypeRelationId:
Rework internals of changing a type's ownership This is necessary so that REASSIGN OWNED does the right thing with composite types, to wit, that it also alters ownership of the type's pg_class entry -- previously, the pg_class entry remained owned by the original user, which caused later other failures such as the new owner's inability to use ALTER TYPE to rename an attribute of the affected composite. Also, if the original owner is later dropped, the pg_class entry becomes owned by a non-existant user which is bogus. To fix, create a new routine AlterTypeOwner_oid which knows whether to pass the request to ATExecChangeOwner or deal with it directly, and use that in shdepReassignOwner rather than calling AlterTypeOwnerInternal directly. AlterTypeOwnerInternal is now simpler in that it only modifies the pg_type entry and recurses to handle a possible array type; higher-level tasks are handled by either AlterTypeOwner directly or AlterTypeOwner_oid. I took the opportunity to add a few more objects to the test rig for REASSIGN OWNED, so that more cases are exercised. Additional ones could be added for superuser-only-ownable objects (such as FDWs and event triggers) but I didn't want to push my luck by adding a new superuser to the tests on a backpatchable bug fix. Per bug #13666 reported by Chris Pacejo. Backpatch to 9.5. (I would back-patch this all the way back, except that it doesn't apply cleanly in 9.4 and earlier because 59367fdf9 wasn't backpatched. If we decide that we need this in earlier branches too, we should backpatch both.)
2015-12-17 18:25:41 +01:00
AlterTypeOwner_oid(sdepForm->objid, newrole, true);
break;
case NamespaceRelationId:
AlterSchemaOwner_oid(sdepForm->objid, newrole);
break;
case RelationRelationId:
2006-10-04 02:30:14 +02:00
/*
* Pass recursing = true so that we don't fail on indexes,
* owned sequences, etc when we happen to visit them
* before their parent table.
*/
ATExecChangeOwner(sdepForm->objid, newrole, true, AccessExclusiveLock);
break;
case DefaultAclRelationId:
2010-02-26 03:01:40 +01:00
/*
* Ignore default ACLs; they should be handled by DROP
* OWNED, not REASSIGN OWNED.
*/
break;
case UserMappingRelationId:
/* ditto */
break;
case ForeignServerRelationId:
AlterForeignServerOwner_oid(sdepForm->objid, newrole);
break;
case ForeignDataWrapperRelationId:
AlterForeignDataWrapperOwner_oid(sdepForm->objid, newrole);
break;
case EventTriggerRelationId:
AlterEventTriggerOwner_oid(sdepForm->objid, newrole);
break;
case PublicationRelationId:
AlterPublicationOwner_oid(sdepForm->objid, newrole);
break;
case SubscriptionRelationId:
AlterSubscriptionOwner_oid(sdepForm->objid, newrole);
break;
/* Generic alter owner cases */
case CollationRelationId:
case ConversionRelationId:
case OperatorRelationId:
case ProcedureRelationId:
case LanguageRelationId:
case LargeObjectRelationId:
case OperatorFamilyRelationId:
case OperatorClassRelationId:
case ExtensionRelationId:
Implement multivariate n-distinct coefficients Add support for explicitly declared statistic objects (CREATE STATISTICS), allowing collection of statistics on more complex combinations that individual table columns. Companion commands DROP STATISTICS and ALTER STATISTICS ... OWNER TO / SET SCHEMA / RENAME are added too. All this DDL has been designed so that more statistic types can be added later on, such as multivariate most-common-values and multivariate histograms between columns of a single table, leaving room for permitting columns on multiple tables, too, as well as expressions. This commit only adds support for collection of n-distinct coefficient on user-specified sets of columns in a single table. This is useful to estimate number of distinct groups in GROUP BY and DISTINCT clauses; estimation errors there can cause over-allocation of memory in hashed aggregates, for instance, so it's a worthwhile problem to solve. A new special pseudo-type pg_ndistinct is used. (num-distinct estimation was deemed sufficiently useful by itself that this is worthwhile even if no further statistic types are added immediately; so much so that another version of essentially the same functionality was submitted by Kyotaro Horiguchi: https://postgr.es/m/20150828.173334.114731693.horiguchi.kyotaro@lab.ntt.co.jp though this commit does not use that code.) Author: Tomas Vondra. Some code rework by Álvaro. Reviewed-by: Dean Rasheed, David Rowley, Kyotaro Horiguchi, Jeff Janes, Ideriha Takeshi Discussion: https://postgr.es/m/543AFA15.4080608@fuzzy.cz https://postgr.es/m/20170320190220.ixlaueanxegqd5gr@alvherre.pgsql
2017-03-24 18:06:10 +01:00
case StatisticExtRelationId:
case TableSpaceRelationId:
case DatabaseRelationId:
case TSConfigRelationId:
case TSDictionaryRelationId:
{
Oid classId = sdepForm->classid;
Relation catalog;
if (classId == LargeObjectRelationId)
classId = LargeObjectMetadataRelationId;
catalog = table_open(classId, RowExclusiveLock);
AlterObjectOwner_internal(catalog, sdepForm->objid,
newrole);
table_close(catalog, NoLock);
}
break;
default:
elog(ERROR, "unexpected classid %u", sdepForm->classid);
break;
}
/* Clean up */
MemoryContextSwitchTo(oldcxt);
MemoryContextDelete(cxt);
/* Make sure the next iteration will see my changes */
CommandCounterIncrement();
}
systable_endscan(scan);
}
table_close(sdepRel, RowExclusiveLock);
}