diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
commit | 46651ce6fe013220ed397add242004d764fc0153 (patch) | |
tree | 6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/backend/commands/alter.c | |
parent | Initial commit. (diff) | |
download | postgresql-14-upstream.tar.xz postgresql-14-upstream.zip |
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/commands/alter.c')
-rw-r--r-- | src/backend/commands/alter.c | 1059 |
1 files changed, 1059 insertions, 0 deletions
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c new file mode 100644 index 0000000..2924949 --- /dev/null +++ b/src/backend/commands/alter.c @@ -0,0 +1,1059 @@ +/*------------------------------------------------------------------------- + * + * alter.c + * Drivers for generic alter commands + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/alter.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/relation.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_conversion.h" +#include "catalog/pg_event_trigger.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_opfamily.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_statistic_ext.h" +#include "catalog/pg_subscription.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_ts_parser.h" +#include "catalog/pg_ts_template.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/trigger.h" +#include "commands/typecmds.h" +#include "commands/user.h" +#include "miscadmin.h" +#include "parser/parse_func.h" +#include "rewrite/rewriteDefine.h" +#include "tcop/utility.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +static Oid AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid); + +/* + * Raise an error to the effect that an object of the given name is already + * present in the given namespace. + */ +static void +report_name_conflict(Oid classId, const char *name) +{ + char *msgfmt; + + switch (classId) + { + case EventTriggerRelationId: + msgfmt = gettext_noop("event trigger \"%s\" already exists"); + break; + case ForeignDataWrapperRelationId: + msgfmt = gettext_noop("foreign-data wrapper \"%s\" already exists"); + break; + case ForeignServerRelationId: + msgfmt = gettext_noop("server \"%s\" already exists"); + break; + case LanguageRelationId: + msgfmt = gettext_noop("language \"%s\" already exists"); + break; + case PublicationRelationId: + msgfmt = gettext_noop("publication \"%s\" already exists"); + break; + case SubscriptionRelationId: + msgfmt = gettext_noop("subscription \"%s\" already exists"); + break; + default: + elog(ERROR, "unsupported object class %u", classId); + break; + } + + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg(msgfmt, name))); +} + +static void +report_namespace_conflict(Oid classId, const char *name, Oid nspOid) +{ + char *msgfmt; + + Assert(OidIsValid(nspOid)); + + switch (classId) + { + case ConversionRelationId: + Assert(OidIsValid(nspOid)); + msgfmt = gettext_noop("conversion \"%s\" already exists in schema \"%s\""); + break; + case StatisticExtRelationId: + Assert(OidIsValid(nspOid)); + msgfmt = gettext_noop("statistics object \"%s\" already exists in schema \"%s\""); + break; + case TSParserRelationId: + Assert(OidIsValid(nspOid)); + msgfmt = gettext_noop("text search parser \"%s\" already exists in schema \"%s\""); + break; + case TSDictionaryRelationId: + Assert(OidIsValid(nspOid)); + msgfmt = gettext_noop("text search dictionary \"%s\" already exists in schema \"%s\""); + break; + case TSTemplateRelationId: + Assert(OidIsValid(nspOid)); + msgfmt = gettext_noop("text search template \"%s\" already exists in schema \"%s\""); + break; + case TSConfigRelationId: + Assert(OidIsValid(nspOid)); + msgfmt = gettext_noop("text search configuration \"%s\" already exists in schema \"%s\""); + break; + default: + elog(ERROR, "unsupported object class %u", classId); + break; + } + + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg(msgfmt, name, get_namespace_name(nspOid)))); +} + +/* + * AlterObjectRename_internal + * + * Generic function to rename the given object, for simple cases (won't + * work for tables, nor other cases where we need to do more than change + * the name column of a single catalog entry). + * + * rel: catalog relation containing object (RowExclusiveLock'd by caller) + * objectId: OID of object to be renamed + * new_name: CString representation of new name + */ +static void +AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name) +{ + Oid classId = RelationGetRelid(rel); + int oidCacheId = get_object_catcache_oid(classId); + int nameCacheId = get_object_catcache_name(classId); + AttrNumber Anum_name = get_object_attnum_name(classId); + AttrNumber Anum_namespace = get_object_attnum_namespace(classId); + AttrNumber Anum_owner = get_object_attnum_owner(classId); + HeapTuple oldtup; + HeapTuple newtup; + Datum datum; + bool isnull; + Oid namespaceId; + Oid ownerId; + char *old_name; + AclResult aclresult; + Datum *values; + bool *nulls; + bool *replaces; + NameData nameattrdata; + + oldtup = SearchSysCache1(oidCacheId, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(oldtup)) + elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"", + objectId, RelationGetRelationName(rel)); + + datum = heap_getattr(oldtup, Anum_name, + RelationGetDescr(rel), &isnull); + Assert(!isnull); + old_name = NameStr(*(DatumGetName(datum))); + + /* Get OID of namespace */ + if (Anum_namespace > 0) + { + datum = heap_getattr(oldtup, Anum_namespace, + RelationGetDescr(rel), &isnull); + Assert(!isnull); + namespaceId = DatumGetObjectId(datum); + } + else + namespaceId = InvalidOid; + + /* Permission checks ... superusers can always do it */ + if (!superuser()) + { + /* Fail if object does not have an explicit owner */ + if (Anum_owner <= 0) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to rename %s", + getObjectDescriptionOids(classId, objectId)))); + + /* Otherwise, must be owner of the existing object */ + datum = heap_getattr(oldtup, Anum_owner, + RelationGetDescr(rel), &isnull); + Assert(!isnull); + ownerId = DatumGetObjectId(datum); + + if (!has_privs_of_role(GetUserId(), DatumGetObjectId(ownerId))) + aclcheck_error(ACLCHECK_NOT_OWNER, get_object_type(classId, objectId), + old_name); + + /* User must have CREATE privilege on the namespace */ + if (OidIsValid(namespaceId)) + { + aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), + ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_SCHEMA, + get_namespace_name(namespaceId)); + } + } + + /* + * Check for duplicate name (more friendly than unique-index failure). + * Since this is just a friendliness check, we can just skip it in cases + * where there isn't suitable support. + */ + if (classId == ProcedureRelationId) + { + Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(oldtup); + + IsThereFunctionInNamespace(new_name, proc->pronargs, + &proc->proargtypes, proc->pronamespace); + } + else if (classId == CollationRelationId) + { + Form_pg_collation coll = (Form_pg_collation) GETSTRUCT(oldtup); + + IsThereCollationInNamespace(new_name, coll->collnamespace); + } + else if (classId == OperatorClassRelationId) + { + Form_pg_opclass opc = (Form_pg_opclass) GETSTRUCT(oldtup); + + IsThereOpClassInNamespace(new_name, opc->opcmethod, + opc->opcnamespace); + } + else if (classId == OperatorFamilyRelationId) + { + Form_pg_opfamily opf = (Form_pg_opfamily) GETSTRUCT(oldtup); + + IsThereOpFamilyInNamespace(new_name, opf->opfmethod, + opf->opfnamespace); + } + else if (classId == SubscriptionRelationId) + { + if (SearchSysCacheExists2(SUBSCRIPTIONNAME, MyDatabaseId, + CStringGetDatum(new_name))) + report_name_conflict(classId, new_name); + + /* Also enforce regression testing naming rules, if enabled */ +#ifdef ENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS + if (strncmp(new_name, "regress_", 8) != 0) + elog(WARNING, "subscriptions created by regression test cases should have names starting with \"regress_\""); +#endif + } + else if (nameCacheId >= 0) + { + if (OidIsValid(namespaceId)) + { + if (SearchSysCacheExists2(nameCacheId, + CStringGetDatum(new_name), + ObjectIdGetDatum(namespaceId))) + report_namespace_conflict(classId, new_name, namespaceId); + } + else + { + if (SearchSysCacheExists1(nameCacheId, + CStringGetDatum(new_name))) + report_name_conflict(classId, new_name); + } + } + + /* Build modified tuple */ + values = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(Datum)); + nulls = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool)); + replaces = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool)); + namestrcpy(&nameattrdata, new_name); + values[Anum_name - 1] = NameGetDatum(&nameattrdata); + replaces[Anum_name - 1] = true; + newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel), + values, nulls, replaces); + + /* Perform actual update */ + CatalogTupleUpdate(rel, &oldtup->t_self, newtup); + + InvokeObjectPostAlterHook(classId, objectId, 0); + + /* Release memory */ + pfree(values); + pfree(nulls); + pfree(replaces); + heap_freetuple(newtup); + + ReleaseSysCache(oldtup); +} + +/* + * Executes an ALTER OBJECT / RENAME TO statement. Based on the object + * type, the function appropriate to that type is executed. + * + * Return value is the address of the renamed object. + */ +ObjectAddress +ExecRenameStmt(RenameStmt *stmt) +{ + switch (stmt->renameType) + { + case OBJECT_TABCONSTRAINT: + case OBJECT_DOMCONSTRAINT: + return RenameConstraint(stmt); + + case OBJECT_DATABASE: + return RenameDatabase(stmt->subname, stmt->newname); + + case OBJECT_ROLE: + return RenameRole(stmt->subname, stmt->newname); + + case OBJECT_SCHEMA: + return RenameSchema(stmt->subname, stmt->newname); + + case OBJECT_TABLESPACE: + return RenameTableSpace(stmt->subname, stmt->newname); + + case OBJECT_TABLE: + case OBJECT_SEQUENCE: + case OBJECT_VIEW: + case OBJECT_MATVIEW: + case OBJECT_INDEX: + case OBJECT_FOREIGN_TABLE: + return RenameRelation(stmt); + + case OBJECT_COLUMN: + case OBJECT_ATTRIBUTE: + return renameatt(stmt); + + case OBJECT_RULE: + return RenameRewriteRule(stmt->relation, stmt->subname, + stmt->newname); + + case OBJECT_TRIGGER: + return renametrig(stmt); + + case OBJECT_POLICY: + return rename_policy(stmt); + + case OBJECT_DOMAIN: + case OBJECT_TYPE: + return RenameType(stmt); + + case OBJECT_AGGREGATE: + case OBJECT_COLLATION: + case OBJECT_CONVERSION: + case OBJECT_EVENT_TRIGGER: + case OBJECT_FDW: + case OBJECT_FOREIGN_SERVER: + case OBJECT_FUNCTION: + case OBJECT_OPCLASS: + case OBJECT_OPFAMILY: + case OBJECT_LANGUAGE: + case OBJECT_PROCEDURE: + case OBJECT_ROUTINE: + case OBJECT_STATISTIC_EXT: + case OBJECT_TSCONFIGURATION: + case OBJECT_TSDICTIONARY: + case OBJECT_TSPARSER: + case OBJECT_TSTEMPLATE: + case OBJECT_PUBLICATION: + case OBJECT_SUBSCRIPTION: + { + ObjectAddress address; + Relation catalog; + Relation relation; + + address = get_object_address(stmt->renameType, + stmt->object, + &relation, + AccessExclusiveLock, false); + Assert(relation == NULL); + + catalog = table_open(address.classId, RowExclusiveLock); + AlterObjectRename_internal(catalog, + address.objectId, + stmt->newname); + table_close(catalog, RowExclusiveLock); + + return address; + } + + default: + elog(ERROR, "unrecognized rename stmt type: %d", + (int) stmt->renameType); + return InvalidObjectAddress; /* keep compiler happy */ + } +} + +/* + * Executes an ALTER OBJECT / [NO] DEPENDS ON EXTENSION statement. + * + * Return value is the address of the altered object. refAddress is an output + * argument which, if not null, receives the address of the object that the + * altered object now depends on. + */ +ObjectAddress +ExecAlterObjectDependsStmt(AlterObjectDependsStmt *stmt, ObjectAddress *refAddress) +{ + ObjectAddress address; + ObjectAddress refAddr; + Relation rel; + + address = + get_object_address_rv(stmt->objectType, stmt->relation, (List *) stmt->object, + &rel, AccessExclusiveLock, false); + + /* + * Verify that the user is entitled to run the command. + * + * We don't check any privileges on the extension, because that's not + * needed. The object owner is stipulating, by running this command, that + * the extension owner can drop the object whenever they feel like it, + * which is not considered a problem. + */ + check_object_ownership(GetUserId(), + stmt->objectType, address, stmt->object, rel); + + /* + * If a relation was involved, it would have been opened and locked. We + * don't need the relation here, but we'll retain the lock until commit. + */ + if (rel) + table_close(rel, NoLock); + + refAddr = get_object_address(OBJECT_EXTENSION, (Node *) stmt->extname, + &rel, AccessExclusiveLock, false); + Assert(rel == NULL); + if (refAddress) + *refAddress = refAddr; + + if (stmt->remove) + { + deleteDependencyRecordsForSpecific(address.classId, address.objectId, + DEPENDENCY_AUTO_EXTENSION, + refAddr.classId, refAddr.objectId); + } + else + { + List *currexts; + + /* Avoid duplicates */ + currexts = getAutoExtensionsOfObject(address.classId, + address.objectId); + if (!list_member_oid(currexts, refAddr.objectId)) + recordDependencyOn(&address, &refAddr, DEPENDENCY_AUTO_EXTENSION); + } + + return address; +} + +/* + * Executes an ALTER OBJECT / SET SCHEMA statement. Based on the object + * type, the function appropriate to that type is executed. + * + * Return value is that of the altered object. + * + * oldSchemaAddr is an output argument which, if not NULL, is set to the object + * address of the original schema. + */ +ObjectAddress +ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt, + ObjectAddress *oldSchemaAddr) +{ + ObjectAddress address; + Oid oldNspOid; + + switch (stmt->objectType) + { + case OBJECT_EXTENSION: + address = AlterExtensionNamespace(strVal((Value *) stmt->object), stmt->newschema, + oldSchemaAddr ? &oldNspOid : NULL); + break; + + case OBJECT_FOREIGN_TABLE: + case OBJECT_SEQUENCE: + case OBJECT_TABLE: + case OBJECT_VIEW: + case OBJECT_MATVIEW: + address = AlterTableNamespace(stmt, + oldSchemaAddr ? &oldNspOid : NULL); + break; + + case OBJECT_DOMAIN: + case OBJECT_TYPE: + address = AlterTypeNamespace(castNode(List, stmt->object), stmt->newschema, + stmt->objectType, + oldSchemaAddr ? &oldNspOid : NULL); + break; + + /* generic code path */ + case OBJECT_AGGREGATE: + case OBJECT_COLLATION: + case OBJECT_CONVERSION: + case OBJECT_FUNCTION: + case OBJECT_OPERATOR: + case OBJECT_OPCLASS: + case OBJECT_OPFAMILY: + case OBJECT_PROCEDURE: + case OBJECT_ROUTINE: + case OBJECT_STATISTIC_EXT: + case OBJECT_TSCONFIGURATION: + case OBJECT_TSDICTIONARY: + case OBJECT_TSPARSER: + case OBJECT_TSTEMPLATE: + { + Relation catalog; + Relation relation; + Oid classId; + Oid nspOid; + + address = get_object_address(stmt->objectType, + stmt->object, + &relation, + AccessExclusiveLock, + false); + Assert(relation == NULL); + classId = address.classId; + catalog = table_open(classId, RowExclusiveLock); + nspOid = LookupCreationNamespace(stmt->newschema); + + oldNspOid = AlterObjectNamespace_internal(catalog, address.objectId, + nspOid); + table_close(catalog, RowExclusiveLock); + } + break; + + default: + elog(ERROR, "unrecognized AlterObjectSchemaStmt type: %d", + (int) stmt->objectType); + return InvalidObjectAddress; /* keep compiler happy */ + } + + if (oldSchemaAddr) + ObjectAddressSet(*oldSchemaAddr, NamespaceRelationId, oldNspOid); + + return address; +} + +/* + * Change an object's namespace given its classOid and object Oid. + * + * Objects that don't have a namespace should be ignored. + * + * This function is currently used only by ALTER EXTENSION SET SCHEMA, + * so it only needs to cover object types that can be members of an + * extension, and it doesn't have to deal with certain special cases + * such as not wanting to process array types --- those should never + * be direct members of an extension anyway. Nonetheless, we insist + * on listing all OCLASS types in the switch. + * + * Returns the OID of the object's previous namespace, or InvalidOid if + * object doesn't have a schema. + */ +Oid +AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid, + ObjectAddresses *objsMoved) +{ + Oid oldNspOid = InvalidOid; + ObjectAddress dep; + + dep.classId = classId; + dep.objectId = objid; + dep.objectSubId = 0; + + switch (getObjectClass(&dep)) + { + case OCLASS_CLASS: + { + Relation rel; + + rel = relation_open(objid, AccessExclusiveLock); + oldNspOid = RelationGetNamespace(rel); + + AlterTableNamespaceInternal(rel, oldNspOid, nspOid, objsMoved); + + relation_close(rel, NoLock); + break; + } + + case OCLASS_TYPE: + oldNspOid = AlterTypeNamespace_oid(objid, nspOid, objsMoved); + break; + + case OCLASS_PROC: + case OCLASS_COLLATION: + case OCLASS_CONVERSION: + case OCLASS_OPERATOR: + case OCLASS_OPCLASS: + case OCLASS_OPFAMILY: + case OCLASS_STATISTIC_EXT: + case OCLASS_TSPARSER: + case OCLASS_TSDICT: + case OCLASS_TSTEMPLATE: + case OCLASS_TSCONFIG: + { + Relation catalog; + + catalog = table_open(classId, RowExclusiveLock); + + oldNspOid = AlterObjectNamespace_internal(catalog, objid, + nspOid); + + table_close(catalog, RowExclusiveLock); + } + break; + + case OCLASS_CAST: + case OCLASS_CONSTRAINT: + case OCLASS_DEFAULT: + case OCLASS_LANGUAGE: + case OCLASS_LARGEOBJECT: + case OCLASS_AM: + case OCLASS_AMOP: + case OCLASS_AMPROC: + case OCLASS_REWRITE: + case OCLASS_TRIGGER: + case OCLASS_SCHEMA: + case OCLASS_ROLE: + case OCLASS_DATABASE: + case OCLASS_TBLSPACE: + case OCLASS_FDW: + case OCLASS_FOREIGN_SERVER: + case OCLASS_USER_MAPPING: + case OCLASS_DEFACL: + case OCLASS_EXTENSION: + case OCLASS_EVENT_TRIGGER: + case OCLASS_POLICY: + case OCLASS_PUBLICATION: + case OCLASS_PUBLICATION_REL: + case OCLASS_SUBSCRIPTION: + case OCLASS_TRANSFORM: + /* ignore object types that don't have schema-qualified names */ + break; + + /* + * There's intentionally no default: case here; we want the + * compiler to warn if a new OCLASS hasn't been handled above. + */ + } + + return oldNspOid; +} + +/* + * Generic function to change the namespace of a given object, for simple + * cases (won't work for tables, nor other cases where we need to do more + * than change the namespace column of a single catalog entry). + * + * rel: catalog relation containing object (RowExclusiveLock'd by caller) + * objid: OID of object to change the namespace of + * nspOid: OID of new namespace + * + * Returns the OID of the object's previous namespace. + */ +static Oid +AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid) +{ + Oid classId = RelationGetRelid(rel); + int oidCacheId = get_object_catcache_oid(classId); + int nameCacheId = get_object_catcache_name(classId); + AttrNumber Anum_name = get_object_attnum_name(classId); + AttrNumber Anum_namespace = get_object_attnum_namespace(classId); + AttrNumber Anum_owner = get_object_attnum_owner(classId); + Oid oldNspOid; + Datum name, + namespace; + bool isnull; + HeapTuple tup, + newtup; + Datum *values; + bool *nulls; + bool *replaces; + + tup = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objid)); + if (!HeapTupleIsValid(tup)) /* should not happen */ + elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"", + objid, RelationGetRelationName(rel)); + + name = heap_getattr(tup, Anum_name, RelationGetDescr(rel), &isnull); + Assert(!isnull); + namespace = heap_getattr(tup, Anum_namespace, RelationGetDescr(rel), + &isnull); + Assert(!isnull); + oldNspOid = DatumGetObjectId(namespace); + + /* + * If the object is already in the correct namespace, we don't need to do + * anything except fire the object access hook. + */ + if (oldNspOid == nspOid) + { + InvokeObjectPostAlterHook(classId, objid, 0); + return oldNspOid; + } + + /* Check basic namespace related issues */ + CheckSetNamespace(oldNspOid, nspOid); + + /* Permission checks ... superusers can always do it */ + if (!superuser()) + { + Datum owner; + Oid ownerId; + AclResult aclresult; + + /* Fail if object does not have an explicit owner */ + if (Anum_owner <= 0) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to set schema of %s", + getObjectDescriptionOids(classId, objid)))); + + /* Otherwise, must be owner of the existing object */ + owner = heap_getattr(tup, Anum_owner, RelationGetDescr(rel), &isnull); + Assert(!isnull); + ownerId = DatumGetObjectId(owner); + + if (!has_privs_of_role(GetUserId(), ownerId)) + aclcheck_error(ACLCHECK_NOT_OWNER, get_object_type(classId, objid), + NameStr(*(DatumGetName(name)))); + + /* User must have CREATE privilege on new namespace */ + aclresult = pg_namespace_aclcheck(nspOid, GetUserId(), ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_SCHEMA, + get_namespace_name(nspOid)); + } + + /* + * Check for duplicate name (more friendly than unique-index failure). + * Since this is just a friendliness check, we can just skip it in cases + * where there isn't suitable support. + */ + if (classId == ProcedureRelationId) + { + Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tup); + + IsThereFunctionInNamespace(NameStr(proc->proname), proc->pronargs, + &proc->proargtypes, nspOid); + } + else if (classId == CollationRelationId) + { + Form_pg_collation coll = (Form_pg_collation) GETSTRUCT(tup); + + IsThereCollationInNamespace(NameStr(coll->collname), nspOid); + } + else if (classId == OperatorClassRelationId) + { + Form_pg_opclass opc = (Form_pg_opclass) GETSTRUCT(tup); + + IsThereOpClassInNamespace(NameStr(opc->opcname), + opc->opcmethod, nspOid); + } + else if (classId == OperatorFamilyRelationId) + { + Form_pg_opfamily opf = (Form_pg_opfamily) GETSTRUCT(tup); + + IsThereOpFamilyInNamespace(NameStr(opf->opfname), + opf->opfmethod, nspOid); + } + else if (nameCacheId >= 0 && + SearchSysCacheExists2(nameCacheId, name, + ObjectIdGetDatum(nspOid))) + report_namespace_conflict(classId, + NameStr(*(DatumGetName(name))), + nspOid); + + /* Build modified tuple */ + values = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(Datum)); + nulls = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool)); + replaces = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool)); + values[Anum_namespace - 1] = ObjectIdGetDatum(nspOid); + replaces[Anum_namespace - 1] = true; + newtup = heap_modify_tuple(tup, RelationGetDescr(rel), + values, nulls, replaces); + + /* Perform actual update */ + CatalogTupleUpdate(rel, &tup->t_self, newtup); + + /* Release memory */ + pfree(values); + pfree(nulls); + pfree(replaces); + + /* update dependencies to point to the new schema */ + changeDependencyFor(classId, objid, + NamespaceRelationId, oldNspOid, nspOid); + + InvokeObjectPostAlterHook(classId, objid, 0); + + return oldNspOid; +} + +/* + * Executes an ALTER OBJECT / OWNER TO statement. Based on the object + * type, the function appropriate to that type is executed. + */ +ObjectAddress +ExecAlterOwnerStmt(AlterOwnerStmt *stmt) +{ + Oid newowner = get_rolespec_oid(stmt->newowner, false); + + switch (stmt->objectType) + { + case OBJECT_DATABASE: + return AlterDatabaseOwner(strVal((Value *) stmt->object), newowner); + + case OBJECT_SCHEMA: + return AlterSchemaOwner(strVal((Value *) stmt->object), newowner); + + case OBJECT_TYPE: + case OBJECT_DOMAIN: /* same as TYPE */ + return AlterTypeOwner(castNode(List, stmt->object), newowner, stmt->objectType); + break; + + case OBJECT_FDW: + return AlterForeignDataWrapperOwner(strVal((Value *) stmt->object), + newowner); + + case OBJECT_FOREIGN_SERVER: + return AlterForeignServerOwner(strVal((Value *) stmt->object), + newowner); + + case OBJECT_EVENT_TRIGGER: + return AlterEventTriggerOwner(strVal((Value *) stmt->object), + newowner); + + case OBJECT_PUBLICATION: + return AlterPublicationOwner(strVal((Value *) stmt->object), + newowner); + + case OBJECT_SUBSCRIPTION: + return AlterSubscriptionOwner(strVal((Value *) stmt->object), + newowner); + + /* Generic cases */ + case OBJECT_AGGREGATE: + case OBJECT_COLLATION: + case OBJECT_CONVERSION: + case OBJECT_FUNCTION: + case OBJECT_LANGUAGE: + case OBJECT_LARGEOBJECT: + case OBJECT_OPERATOR: + case OBJECT_OPCLASS: + case OBJECT_OPFAMILY: + case OBJECT_PROCEDURE: + case OBJECT_ROUTINE: + case OBJECT_STATISTIC_EXT: + case OBJECT_TABLESPACE: + case OBJECT_TSDICTIONARY: + case OBJECT_TSCONFIGURATION: + { + Relation catalog; + Relation relation; + Oid classId; + ObjectAddress address; + + address = get_object_address(stmt->objectType, + stmt->object, + &relation, + AccessExclusiveLock, + false); + Assert(relation == NULL); + classId = address.classId; + + /* + * XXX - get_object_address returns Oid of pg_largeobject + * catalog for OBJECT_LARGEOBJECT because of historical + * reasons. Fix up it here. + */ + if (classId == LargeObjectRelationId) + classId = LargeObjectMetadataRelationId; + + catalog = table_open(classId, RowExclusiveLock); + + AlterObjectOwner_internal(catalog, address.objectId, newowner); + table_close(catalog, RowExclusiveLock); + + return address; + } + break; + + default: + elog(ERROR, "unrecognized AlterOwnerStmt type: %d", + (int) stmt->objectType); + return InvalidObjectAddress; /* keep compiler happy */ + } +} + +/* + * Generic function to change the ownership of a given object, for simple + * cases (won't work for tables, nor other cases where we need to do more than + * change the ownership column of a single catalog entry). + * + * rel: catalog relation containing object (RowExclusiveLock'd by caller) + * objectId: OID of object to change the ownership of + * new_ownerId: OID of new object owner + */ +void +AlterObjectOwner_internal(Relation rel, Oid objectId, Oid new_ownerId) +{ + Oid classId = RelationGetRelid(rel); + AttrNumber Anum_oid = get_object_attnum_oid(classId); + AttrNumber Anum_owner = get_object_attnum_owner(classId); + AttrNumber Anum_namespace = get_object_attnum_namespace(classId); + AttrNumber Anum_acl = get_object_attnum_acl(classId); + AttrNumber Anum_name = get_object_attnum_name(classId); + HeapTuple oldtup; + Datum datum; + bool isnull; + Oid old_ownerId; + Oid namespaceId = InvalidOid; + + oldtup = get_catalog_object_by_oid(rel, Anum_oid, objectId); + if (oldtup == NULL) + elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"", + objectId, RelationGetRelationName(rel)); + + datum = heap_getattr(oldtup, Anum_owner, + RelationGetDescr(rel), &isnull); + Assert(!isnull); + old_ownerId = DatumGetObjectId(datum); + + if (Anum_namespace != InvalidAttrNumber) + { + datum = heap_getattr(oldtup, Anum_namespace, + RelationGetDescr(rel), &isnull); + Assert(!isnull); + namespaceId = DatumGetObjectId(datum); + } + + if (old_ownerId != new_ownerId) + { + AttrNumber nattrs; + HeapTuple newtup; + Datum *values; + bool *nulls; + bool *replaces; + + /* Superusers can bypass permission checks */ + if (!superuser()) + { + /* must be owner */ + if (!has_privs_of_role(GetUserId(), old_ownerId)) + { + char *objname; + char namebuf[NAMEDATALEN]; + + if (Anum_name != InvalidAttrNumber) + { + datum = heap_getattr(oldtup, Anum_name, + RelationGetDescr(rel), &isnull); + Assert(!isnull); + objname = NameStr(*DatumGetName(datum)); + } + else + { + snprintf(namebuf, sizeof(namebuf), "%u", objectId); + objname = namebuf; + } + aclcheck_error(ACLCHECK_NOT_OWNER, get_object_type(classId, objectId), + objname); + } + /* Must be able to become new owner */ + check_is_member_of_role(GetUserId(), new_ownerId); + + /* New owner must have CREATE privilege on namespace */ + if (OidIsValid(namespaceId)) + { + AclResult aclresult; + + aclresult = pg_namespace_aclcheck(namespaceId, new_ownerId, + ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_SCHEMA, + get_namespace_name(namespaceId)); + } + } + + /* Build a modified tuple */ + nattrs = RelationGetNumberOfAttributes(rel); + values = palloc0(nattrs * sizeof(Datum)); + nulls = palloc0(nattrs * sizeof(bool)); + replaces = palloc0(nattrs * sizeof(bool)); + values[Anum_owner - 1] = ObjectIdGetDatum(new_ownerId); + replaces[Anum_owner - 1] = true; + + /* + * Determine the modified ACL for the new owner. This is only + * necessary when the ACL is non-null. + */ + if (Anum_acl != InvalidAttrNumber) + { + datum = heap_getattr(oldtup, + Anum_acl, RelationGetDescr(rel), &isnull); + if (!isnull) + { + Acl *newAcl; + + newAcl = aclnewowner(DatumGetAclP(datum), + old_ownerId, new_ownerId); + values[Anum_acl - 1] = PointerGetDatum(newAcl); + replaces[Anum_acl - 1] = true; + } + } + + newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel), + values, nulls, replaces); + + /* Perform actual update */ + CatalogTupleUpdate(rel, &newtup->t_self, newtup); + + /* Update owner dependency reference */ + if (classId == LargeObjectMetadataRelationId) + classId = LargeObjectRelationId; + changeDependencyOnOwner(classId, objectId, new_ownerId); + + /* Release memory */ + pfree(values); + pfree(nulls); + pfree(replaces); + } + + InvokeObjectPostAlterHook(classId, objectId, 0); +} |