summaryrefslogtreecommitdiffstats
path: root/src/backend/commands/alter.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
commit46651ce6fe013220ed397add242004d764fc0153 (patch)
tree6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/backend/commands/alter.c
parentInitial commit. (diff)
downloadpostgresql-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.c1059
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);
+}