/*------------------------------------------------------------------------- * * 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 * 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" #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" #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 * 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). * (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); 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); } /* * 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)); 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 * * 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); 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; } 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); 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: AlterTypeOwner_oid(sdepForm->objid, newrole, true); break; case NamespaceRelationId: AlterSchemaOwner_oid(sdepForm->objid, newrole); break; case RelationRelationId: /* * 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: /* * 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: case StatisticExtRelationId: case TableSpaceRelationId: case DatabaseRelationId: case TSConfigRelationId: case TSDictionaryRelationId: { Oid classId = sdepForm->classid; Relation catalog; /* * For large objects, the catalog to modify is * pg_largeobject_metadata */ 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); }