Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * alter.c
4 : * Drivers for generic alter commands
5 : *
6 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/backend/commands/alter.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include "access/htup_details.h"
18 : #include "access/relation.h"
19 : #include "access/table.h"
20 : #include "catalog/dependency.h"
21 : #include "catalog/indexing.h"
22 : #include "catalog/namespace.h"
23 : #include "catalog/objectaccess.h"
24 : #include "catalog/pg_collation.h"
25 : #include "catalog/pg_conversion.h"
26 : #include "catalog/pg_database_d.h"
27 : #include "catalog/pg_event_trigger.h"
28 : #include "catalog/pg_foreign_data_wrapper.h"
29 : #include "catalog/pg_foreign_server.h"
30 : #include "catalog/pg_language.h"
31 : #include "catalog/pg_largeobject.h"
32 : #include "catalog/pg_largeobject_metadata.h"
33 : #include "catalog/pg_namespace.h"
34 : #include "catalog/pg_opclass.h"
35 : #include "catalog/pg_operator.h"
36 : #include "catalog/pg_opfamily.h"
37 : #include "catalog/pg_proc.h"
38 : #include "catalog/pg_statistic_ext.h"
39 : #include "catalog/pg_subscription.h"
40 : #include "catalog/pg_ts_config.h"
41 : #include "catalog/pg_ts_dict.h"
42 : #include "catalog/pg_ts_parser.h"
43 : #include "catalog/pg_ts_template.h"
44 : #include "commands/alter.h"
45 : #include "commands/collationcmds.h"
46 : #include "commands/dbcommands.h"
47 : #include "commands/defrem.h"
48 : #include "commands/event_trigger.h"
49 : #include "commands/extension.h"
50 : #include "commands/policy.h"
51 : #include "commands/publicationcmds.h"
52 : #include "commands/schemacmds.h"
53 : #include "commands/subscriptioncmds.h"
54 : #include "commands/tablecmds.h"
55 : #include "commands/tablespace.h"
56 : #include "commands/trigger.h"
57 : #include "commands/typecmds.h"
58 : #include "commands/user.h"
59 : #include "miscadmin.h"
60 : #include "replication/logicalworker.h"
61 : #include "rewrite/rewriteDefine.h"
62 : #include "storage/lmgr.h"
63 : #include "utils/acl.h"
64 : #include "utils/builtins.h"
65 : #include "utils/lsyscache.h"
66 : #include "utils/rel.h"
67 : #include "utils/syscache.h"
68 :
69 : static Oid AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid);
70 :
71 : /*
72 : * Raise an error to the effect that an object of the given name is already
73 : * present in the given namespace.
74 : */
75 : static void
76 16 : report_name_conflict(Oid classId, const char *name)
77 : {
78 : char *msgfmt;
79 :
80 16 : switch (classId)
81 : {
82 4 : case EventTriggerRelationId:
83 4 : msgfmt = gettext_noop("event trigger \"%s\" already exists");
84 4 : break;
85 4 : case ForeignDataWrapperRelationId:
86 4 : msgfmt = gettext_noop("foreign-data wrapper \"%s\" already exists");
87 4 : break;
88 4 : case ForeignServerRelationId:
89 4 : msgfmt = gettext_noop("server \"%s\" already exists");
90 4 : break;
91 4 : case LanguageRelationId:
92 4 : msgfmt = gettext_noop("language \"%s\" already exists");
93 4 : break;
94 0 : case PublicationRelationId:
95 0 : msgfmt = gettext_noop("publication \"%s\" already exists");
96 0 : break;
97 0 : case SubscriptionRelationId:
98 0 : msgfmt = gettext_noop("subscription \"%s\" already exists");
99 0 : break;
100 0 : default:
101 0 : elog(ERROR, "unsupported object class: %u", classId);
102 : break;
103 : }
104 :
105 16 : ereport(ERROR,
106 : (errcode(ERRCODE_DUPLICATE_OBJECT),
107 : errmsg(msgfmt, name)));
108 : }
109 :
110 : static void
111 48 : report_namespace_conflict(Oid classId, const char *name, Oid nspOid)
112 : {
113 : char *msgfmt;
114 :
115 : Assert(OidIsValid(nspOid));
116 :
117 48 : switch (classId)
118 : {
119 8 : case ConversionRelationId:
120 8 : msgfmt = gettext_noop("conversion \"%s\" already exists in schema \"%s\"");
121 8 : break;
122 8 : case StatisticExtRelationId:
123 8 : msgfmt = gettext_noop("statistics object \"%s\" already exists in schema \"%s\"");
124 8 : break;
125 8 : case TSParserRelationId:
126 8 : msgfmt = gettext_noop("text search parser \"%s\" already exists in schema \"%s\"");
127 8 : break;
128 8 : case TSDictionaryRelationId:
129 8 : msgfmt = gettext_noop("text search dictionary \"%s\" already exists in schema \"%s\"");
130 8 : break;
131 8 : case TSTemplateRelationId:
132 8 : msgfmt = gettext_noop("text search template \"%s\" already exists in schema \"%s\"");
133 8 : break;
134 8 : case TSConfigRelationId:
135 8 : msgfmt = gettext_noop("text search configuration \"%s\" already exists in schema \"%s\"");
136 8 : break;
137 0 : default:
138 0 : elog(ERROR, "unsupported object class: %u", classId);
139 : break;
140 : }
141 :
142 48 : ereport(ERROR,
143 : (errcode(ERRCODE_DUPLICATE_OBJECT),
144 : errmsg(msgfmt, name, get_namespace_name(nspOid))));
145 : }
146 :
147 : /*
148 : * AlterObjectRename_internal
149 : *
150 : * Generic function to rename the given object, for simple cases (won't
151 : * work for tables, nor other cases where we need to do more than change
152 : * the name column of a single catalog entry).
153 : *
154 : * rel: catalog relation containing object (RowExclusiveLock'd by caller)
155 : * objectId: OID of object to be renamed
156 : * new_name: CString representation of new name
157 : */
158 : static void
159 281 : AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
160 : {
161 281 : Oid classId = RelationGetRelid(rel);
162 281 : SysCacheIdentifier oidCacheId = get_object_catcache_oid(classId);
163 281 : SysCacheIdentifier nameCacheId = get_object_catcache_name(classId);
164 281 : AttrNumber Anum_name = get_object_attnum_name(classId);
165 281 : AttrNumber Anum_namespace = get_object_attnum_namespace(classId);
166 281 : AttrNumber Anum_owner = get_object_attnum_owner(classId);
167 : HeapTuple oldtup;
168 : HeapTuple newtup;
169 : Datum datum;
170 : bool isnull;
171 : Oid namespaceId;
172 : Oid ownerId;
173 : char *old_name;
174 : AclResult aclresult;
175 : Datum *values;
176 : bool *nulls;
177 : bool *replaces;
178 : NameData nameattrdata;
179 :
180 281 : oldtup = SearchSysCache1(oidCacheId, ObjectIdGetDatum(objectId));
181 281 : if (!HeapTupleIsValid(oldtup))
182 0 : elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
183 : objectId, RelationGetRelationName(rel));
184 :
185 281 : datum = heap_getattr(oldtup, Anum_name,
186 : RelationGetDescr(rel), &isnull);
187 : Assert(!isnull);
188 281 : old_name = NameStr(*(DatumGetName(datum)));
189 :
190 : /* Get OID of namespace */
191 281 : if (Anum_namespace > 0)
192 : {
193 180 : datum = heap_getattr(oldtup, Anum_namespace,
194 : RelationGetDescr(rel), &isnull);
195 : Assert(!isnull);
196 180 : namespaceId = DatumGetObjectId(datum);
197 : }
198 : else
199 101 : namespaceId = InvalidOid;
200 :
201 : /* Permission checks ... superusers can always do it */
202 281 : if (!superuser())
203 : {
204 : /* Fail if object does not have an explicit owner */
205 164 : if (Anum_owner <= 0)
206 0 : ereport(ERROR,
207 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
208 : errmsg("must be superuser to rename %s",
209 : getObjectDescriptionOids(classId, objectId))));
210 :
211 : /* Otherwise, must be owner of the existing object */
212 164 : datum = heap_getattr(oldtup, Anum_owner,
213 : RelationGetDescr(rel), &isnull);
214 : Assert(!isnull);
215 164 : ownerId = DatumGetObjectId(datum);
216 :
217 164 : if (!has_privs_of_role(GetUserId(), ownerId))
218 48 : aclcheck_error(ACLCHECK_NOT_OWNER, get_object_type(classId, objectId),
219 : old_name);
220 :
221 : /* User must have CREATE privilege on the namespace */
222 116 : if (OidIsValid(namespaceId))
223 : {
224 96 : aclresult = object_aclcheck(NamespaceRelationId, namespaceId, GetUserId(),
225 : ACL_CREATE);
226 96 : if (aclresult != ACLCHECK_OK)
227 0 : aclcheck_error(aclresult, OBJECT_SCHEMA,
228 0 : get_namespace_name(namespaceId));
229 : }
230 :
231 116 : if (classId == SubscriptionRelationId)
232 : {
233 : Form_pg_subscription form;
234 :
235 : /* must have CREATE privilege on database */
236 12 : aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId,
237 : GetUserId(), ACL_CREATE);
238 12 : if (aclresult != ACLCHECK_OK)
239 4 : aclcheck_error(aclresult, OBJECT_DATABASE,
240 4 : get_database_name(MyDatabaseId));
241 :
242 : /*
243 : * Don't allow non-superuser modification of a subscription with
244 : * password_required=false.
245 : */
246 8 : form = (Form_pg_subscription) GETSTRUCT(oldtup);
247 8 : if (!form->subpasswordrequired && !superuser())
248 0 : ereport(ERROR,
249 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
250 : errmsg("password_required=false is superuser-only"),
251 : errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
252 : }
253 : }
254 :
255 : /*
256 : * Check for duplicate name (more friendly than unique-index failure).
257 : * Since this is just a friendliness check, we can just skip it in cases
258 : * where there isn't suitable support.
259 : */
260 229 : if (classId == ProcedureRelationId)
261 : {
262 48 : Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(oldtup);
263 :
264 48 : IsThereFunctionInNamespace(new_name, proc->pronargs,
265 : &proc->proargtypes, proc->pronamespace);
266 : }
267 181 : else if (classId == CollationRelationId)
268 : {
269 8 : Form_pg_collation coll = (Form_pg_collation) GETSTRUCT(oldtup);
270 :
271 8 : IsThereCollationInNamespace(new_name, coll->collnamespace);
272 : }
273 173 : else if (classId == OperatorClassRelationId)
274 : {
275 12 : Form_pg_opclass opc = (Form_pg_opclass) GETSTRUCT(oldtup);
276 :
277 12 : IsThereOpClassInNamespace(new_name, opc->opcmethod,
278 : opc->opcnamespace);
279 : }
280 161 : else if (classId == OperatorFamilyRelationId)
281 : {
282 12 : Form_pg_opfamily opf = (Form_pg_opfamily) GETSTRUCT(oldtup);
283 :
284 12 : IsThereOpFamilyInNamespace(new_name, opf->opfmethod,
285 : opf->opfnamespace);
286 : }
287 149 : else if (classId == SubscriptionRelationId)
288 : {
289 17 : if (SearchSysCacheExists2(SUBSCRIPTIONNAME,
290 : ObjectIdGetDatum(MyDatabaseId),
291 : CStringGetDatum(new_name)))
292 0 : report_name_conflict(classId, new_name);
293 :
294 : /* Also enforce regression testing naming rules, if enabled */
295 : #ifdef ENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS
296 : if (strncmp(new_name, "regress_", 8) != 0)
297 : elog(WARNING, "subscriptions created by regression test cases should have names starting with \"regress_\"");
298 : #endif
299 :
300 : /* Wake up related replication workers to handle this change quickly */
301 17 : LogicalRepWorkersWakeupAtCommit(objectId);
302 : }
303 132 : else if (nameCacheId >= 0)
304 : {
305 132 : if (OidIsValid(namespaceId))
306 : {
307 64 : if (SearchSysCacheExists2(nameCacheId,
308 : CStringGetDatum(new_name),
309 : ObjectIdGetDatum(namespaceId)))
310 24 : report_namespace_conflict(classId, new_name, namespaceId);
311 : }
312 : else
313 : {
314 68 : if (SearchSysCacheExists1(nameCacheId,
315 : CStringGetDatum(new_name)))
316 16 : report_name_conflict(classId, new_name);
317 : }
318 : }
319 :
320 : /* Build modified tuple */
321 169 : values = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(Datum));
322 169 : nulls = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool));
323 169 : replaces = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool));
324 169 : namestrcpy(&nameattrdata, new_name);
325 169 : values[Anum_name - 1] = NameGetDatum(&nameattrdata);
326 169 : replaces[Anum_name - 1] = true;
327 169 : newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel),
328 : values, nulls, replaces);
329 :
330 : /* Perform actual update */
331 169 : CatalogTupleUpdate(rel, &oldtup->t_self, newtup);
332 :
333 169 : InvokeObjectPostAlterHook(classId, objectId, 0);
334 :
335 : /* Do post catalog-update tasks */
336 169 : if (classId == PublicationRelationId)
337 : {
338 20 : Form_pg_publication pub = (Form_pg_publication) GETSTRUCT(oldtup);
339 :
340 : /*
341 : * Invalidate relsynccache entries.
342 : *
343 : * Unlike ALTER PUBLICATION ADD/SET/DROP commands, renaming a
344 : * publication does not impact the publication status of tables. So,
345 : * we don't need to invalidate relcache to rebuild the rd_pubdesc.
346 : * Instead, we invalidate only the relsyncache.
347 : */
348 20 : InvalidatePubRelSyncCache(pub->oid, pub->puballtables);
349 : }
350 :
351 : /* Release memory */
352 169 : pfree(values);
353 169 : pfree(nulls);
354 169 : pfree(replaces);
355 169 : heap_freetuple(newtup);
356 :
357 169 : ReleaseSysCache(oldtup);
358 169 : }
359 :
360 : /*
361 : * Executes an ALTER OBJECT / RENAME TO statement. Based on the object
362 : * type, the function appropriate to that type is executed.
363 : *
364 : * Return value is the address of the renamed object.
365 : */
366 : ObjectAddress
367 1010 : ExecRenameStmt(RenameStmt *stmt)
368 : {
369 1010 : switch (stmt->renameType)
370 : {
371 56 : case OBJECT_TABCONSTRAINT:
372 : case OBJECT_DOMCONSTRAINT:
373 56 : return RenameConstraint(stmt);
374 :
375 9 : case OBJECT_DATABASE:
376 9 : return RenameDatabase(stmt->subname, stmt->newname);
377 :
378 20 : case OBJECT_ROLE:
379 20 : return RenameRole(stmt->subname, stmt->newname);
380 :
381 13 : case OBJECT_SCHEMA:
382 13 : return RenameSchema(stmt->subname, stmt->newname);
383 :
384 6 : case OBJECT_TABLESPACE:
385 6 : return RenameTableSpace(stmt->subname, stmt->newname);
386 :
387 327 : case OBJECT_TABLE:
388 : case OBJECT_SEQUENCE:
389 : case OBJECT_VIEW:
390 : case OBJECT_MATVIEW:
391 : case OBJECT_INDEX:
392 : case OBJECT_FOREIGN_TABLE:
393 : case OBJECT_PROPGRAPH:
394 327 : return RenameRelation(stmt);
395 :
396 209 : case OBJECT_COLUMN:
397 : case OBJECT_ATTRIBUTE:
398 209 : return renameatt(stmt);
399 :
400 22 : case OBJECT_RULE:
401 22 : return RenameRewriteRule(stmt->relation, stmt->subname,
402 22 : stmt->newname);
403 :
404 26 : case OBJECT_TRIGGER:
405 26 : return renametrig(stmt);
406 :
407 12 : case OBJECT_POLICY:
408 12 : return rename_policy(stmt);
409 :
410 21 : case OBJECT_DOMAIN:
411 : case OBJECT_TYPE:
412 21 : return RenameType(stmt);
413 :
414 289 : case OBJECT_AGGREGATE:
415 : case OBJECT_COLLATION:
416 : case OBJECT_CONVERSION:
417 : case OBJECT_EVENT_TRIGGER:
418 : case OBJECT_FDW:
419 : case OBJECT_FOREIGN_SERVER:
420 : case OBJECT_FUNCTION:
421 : case OBJECT_OPCLASS:
422 : case OBJECT_OPFAMILY:
423 : case OBJECT_LANGUAGE:
424 : case OBJECT_PROCEDURE:
425 : case OBJECT_ROUTINE:
426 : case OBJECT_STATISTIC_EXT:
427 : case OBJECT_TSCONFIGURATION:
428 : case OBJECT_TSDICTIONARY:
429 : case OBJECT_TSPARSER:
430 : case OBJECT_TSTEMPLATE:
431 : case OBJECT_PUBLICATION:
432 : case OBJECT_SUBSCRIPTION:
433 : {
434 : ObjectAddress address;
435 : Relation catalog;
436 :
437 289 : address = get_object_address(stmt->renameType,
438 : stmt->object,
439 : NULL,
440 : AccessExclusiveLock, false);
441 :
442 281 : catalog = table_open(address.classId, RowExclusiveLock);
443 281 : AlterObjectRename_internal(catalog,
444 : address.objectId,
445 281 : stmt->newname);
446 169 : table_close(catalog, RowExclusiveLock);
447 :
448 169 : return address;
449 : }
450 :
451 0 : default:
452 0 : elog(ERROR, "unrecognized rename stmt type: %d",
453 : (int) stmt->renameType);
454 : return InvalidObjectAddress; /* keep compiler happy */
455 : }
456 : }
457 :
458 : /*
459 : * Executes an ALTER OBJECT / [NO] DEPENDS ON EXTENSION statement.
460 : *
461 : * Return value is the address of the altered object. refAddress is an output
462 : * argument which, if not null, receives the address of the object that the
463 : * altered object now depends on.
464 : */
465 : ObjectAddress
466 23 : ExecAlterObjectDependsStmt(AlterObjectDependsStmt *stmt, ObjectAddress *refAddress)
467 : {
468 : ObjectAddress address;
469 : ObjectAddress refAddr;
470 : Relation rel;
471 :
472 : address =
473 23 : get_object_address_rv(stmt->objectType, stmt->relation, (List *) stmt->object,
474 : &rel, AccessExclusiveLock, false);
475 :
476 : /*
477 : * Verify that the user is entitled to run the command.
478 : *
479 : * We don't check any privileges on the extension, because that's not
480 : * needed. The object owner is stipulating, by running this command, that
481 : * the extension owner can drop the object whenever they feel like it,
482 : * which is not considered a problem.
483 : */
484 23 : check_object_ownership(GetUserId(),
485 : stmt->objectType, address, stmt->object, rel);
486 :
487 : /*
488 : * If a relation was involved, it would have been opened and locked. We
489 : * don't need the relation here, but we'll retain the lock until commit.
490 : */
491 23 : if (rel)
492 17 : table_close(rel, NoLock);
493 :
494 23 : refAddr = get_object_address(OBJECT_EXTENSION, (Node *) stmt->extname,
495 : NULL, AccessExclusiveLock, false);
496 23 : if (refAddress)
497 23 : *refAddress = refAddr;
498 :
499 23 : if (stmt->remove)
500 : {
501 4 : deleteDependencyRecordsForSpecific(address.classId, address.objectId,
502 : DEPENDENCY_AUTO_EXTENSION,
503 : refAddr.classId, refAddr.objectId);
504 : }
505 : else
506 : {
507 : List *currexts;
508 :
509 : /* Avoid duplicates */
510 19 : currexts = getAutoExtensionsOfObject(address.classId,
511 : address.objectId);
512 19 : if (!list_member_oid(currexts, refAddr.objectId))
513 18 : recordDependencyOn(&address, &refAddr, DEPENDENCY_AUTO_EXTENSION);
514 : }
515 :
516 23 : return address;
517 : }
518 :
519 : /*
520 : * Executes an ALTER OBJECT / SET SCHEMA statement. Based on the object
521 : * type, the function appropriate to that type is executed.
522 : *
523 : * Return value is that of the altered object.
524 : *
525 : * oldSchemaAddr is an output argument which, if not NULL, is set to the object
526 : * address of the original schema.
527 : */
528 : ObjectAddress
529 281 : ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
530 : ObjectAddress *oldSchemaAddr)
531 : {
532 : ObjectAddress address;
533 : Oid oldNspOid;
534 :
535 281 : switch (stmt->objectType)
536 : {
537 6 : case OBJECT_EXTENSION:
538 6 : address = AlterExtensionNamespace(strVal(stmt->object), stmt->newschema,
539 : oldSchemaAddr ? &oldNspOid : NULL);
540 4 : break;
541 :
542 88 : case OBJECT_FOREIGN_TABLE:
543 : case OBJECT_SEQUENCE:
544 : case OBJECT_TABLE:
545 : case OBJECT_VIEW:
546 : case OBJECT_MATVIEW:
547 : case OBJECT_PROPGRAPH:
548 88 : address = AlterTableNamespace(stmt,
549 : oldSchemaAddr ? &oldNspOid : NULL);
550 75 : break;
551 :
552 12 : case OBJECT_DOMAIN:
553 : case OBJECT_TYPE:
554 12 : address = AlterTypeNamespace(castNode(List, stmt->object), stmt->newschema,
555 : stmt->objectType,
556 : oldSchemaAddr ? &oldNspOid : NULL);
557 12 : break;
558 :
559 : /* generic code path */
560 175 : case OBJECT_AGGREGATE:
561 : case OBJECT_COLLATION:
562 : case OBJECT_CONVERSION:
563 : case OBJECT_FUNCTION:
564 : case OBJECT_OPERATOR:
565 : case OBJECT_OPCLASS:
566 : case OBJECT_OPFAMILY:
567 : case OBJECT_PROCEDURE:
568 : case OBJECT_ROUTINE:
569 : case OBJECT_STATISTIC_EXT:
570 : case OBJECT_TSCONFIGURATION:
571 : case OBJECT_TSDICTIONARY:
572 : case OBJECT_TSPARSER:
573 : case OBJECT_TSTEMPLATE:
574 : {
575 : Relation catalog;
576 : Oid classId;
577 : Oid nspOid;
578 :
579 175 : address = get_object_address(stmt->objectType,
580 : stmt->object,
581 : NULL,
582 : AccessExclusiveLock,
583 : false);
584 171 : classId = address.classId;
585 171 : catalog = table_open(classId, RowExclusiveLock);
586 171 : nspOid = LookupCreationNamespace(stmt->newschema);
587 :
588 171 : oldNspOid = AlterObjectNamespace_internal(catalog, address.objectId,
589 : nspOid);
590 95 : table_close(catalog, RowExclusiveLock);
591 : }
592 95 : break;
593 :
594 0 : default:
595 0 : elog(ERROR, "unrecognized AlterObjectSchemaStmt type: %d",
596 : (int) stmt->objectType);
597 : return InvalidObjectAddress; /* keep compiler happy */
598 : }
599 :
600 186 : if (oldSchemaAddr)
601 186 : ObjectAddressSet(*oldSchemaAddr, NamespaceRelationId, oldNspOid);
602 :
603 186 : return address;
604 : }
605 :
606 : /*
607 : * Change an object's namespace given its classOid and object Oid.
608 : *
609 : * Objects that don't have a namespace should be ignored, as should
610 : * dependent types such as array types.
611 : *
612 : * This function is currently used only by ALTER EXTENSION SET SCHEMA,
613 : * so it only needs to cover object kinds that can be members of an
614 : * extension, and it can silently ignore dependent types --- we assume
615 : * those will be moved when their parent object is moved.
616 : *
617 : * Returns the OID of the object's previous namespace, or InvalidOid if
618 : * object doesn't have a schema or was ignored due to being a dependent type.
619 : */
620 : Oid
621 21 : AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
622 : ObjectAddresses *objsMoved)
623 : {
624 21 : Oid oldNspOid = InvalidOid;
625 :
626 21 : switch (classId)
627 : {
628 1 : case RelationRelationId:
629 : {
630 : Relation rel;
631 :
632 1 : rel = relation_open(objid, AccessExclusiveLock);
633 1 : oldNspOid = RelationGetNamespace(rel);
634 :
635 1 : AlterTableNamespaceInternal(rel, oldNspOid, nspOid, objsMoved);
636 :
637 1 : relation_close(rel, NoLock);
638 1 : break;
639 : }
640 :
641 8 : case TypeRelationId:
642 8 : oldNspOid = AlterTypeNamespace_oid(objid, nspOid, true, objsMoved);
643 8 : break;
644 :
645 11 : case ProcedureRelationId:
646 : case CollationRelationId:
647 : case ConversionRelationId:
648 : case OperatorRelationId:
649 : case OperatorClassRelationId:
650 : case OperatorFamilyRelationId:
651 : case StatisticExtRelationId:
652 : case TSParserRelationId:
653 : case TSDictionaryRelationId:
654 : case TSTemplateRelationId:
655 : case TSConfigRelationId:
656 : {
657 : Relation catalog;
658 :
659 11 : catalog = table_open(classId, RowExclusiveLock);
660 :
661 11 : oldNspOid = AlterObjectNamespace_internal(catalog, objid,
662 : nspOid);
663 :
664 11 : table_close(catalog, RowExclusiveLock);
665 : }
666 11 : break;
667 :
668 21 : default:
669 : /* ignore object types that don't have schema-qualified names */
670 : Assert(get_object_attnum_namespace(classId) == InvalidAttrNumber);
671 : }
672 :
673 21 : return oldNspOid;
674 : }
675 :
676 : /*
677 : * Generic function to change the namespace of a given object, for simple
678 : * cases (won't work for tables, nor other cases where we need to do more
679 : * than change the namespace column of a single catalog entry).
680 : *
681 : * rel: catalog relation containing object (RowExclusiveLock'd by caller)
682 : * objid: OID of object to change the namespace of
683 : * nspOid: OID of new namespace
684 : *
685 : * Returns the OID of the object's previous namespace.
686 : */
687 : static Oid
688 182 : AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid)
689 : {
690 182 : Oid classId = RelationGetRelid(rel);
691 182 : SysCacheIdentifier oidCacheId = get_object_catcache_oid(classId);
692 182 : SysCacheIdentifier nameCacheId = get_object_catcache_name(classId);
693 182 : AttrNumber Anum_name = get_object_attnum_name(classId);
694 182 : AttrNumber Anum_namespace = get_object_attnum_namespace(classId);
695 182 : AttrNumber Anum_owner = get_object_attnum_owner(classId);
696 : Oid oldNspOid;
697 : Datum name,
698 : namespace;
699 : bool isnull;
700 : HeapTuple tup,
701 : newtup;
702 : Datum *values;
703 : bool *nulls;
704 : bool *replaces;
705 :
706 182 : tup = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objid));
707 182 : if (!HeapTupleIsValid(tup)) /* should not happen */
708 0 : elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
709 : objid, RelationGetRelationName(rel));
710 :
711 182 : name = heap_getattr(tup, Anum_name, RelationGetDescr(rel), &isnull);
712 : Assert(!isnull);
713 182 : namespace = heap_getattr(tup, Anum_namespace, RelationGetDescr(rel),
714 : &isnull);
715 : Assert(!isnull);
716 182 : oldNspOid = DatumGetObjectId(namespace);
717 :
718 : /*
719 : * If the object is already in the correct namespace, we don't need to do
720 : * anything except fire the object access hook.
721 : */
722 182 : if (oldNspOid == nspOid)
723 : {
724 4 : InvokeObjectPostAlterHook(classId, objid, 0);
725 4 : return oldNspOid;
726 : }
727 :
728 : /* Check basic namespace related issues */
729 178 : CheckSetNamespace(oldNspOid, nspOid);
730 :
731 : /* Permission checks ... superusers can always do it */
732 178 : if (!superuser())
733 : {
734 : Datum owner;
735 : Oid ownerId;
736 : AclResult aclresult;
737 :
738 : /* Fail if object does not have an explicit owner */
739 104 : if (Anum_owner <= 0)
740 0 : ereport(ERROR,
741 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
742 : errmsg("must be superuser to set schema of %s",
743 : getObjectDescriptionOids(classId, objid))));
744 :
745 : /* Otherwise, must be owner of the existing object */
746 104 : owner = heap_getattr(tup, Anum_owner, RelationGetDescr(rel), &isnull);
747 : Assert(!isnull);
748 104 : ownerId = DatumGetObjectId(owner);
749 :
750 104 : if (!has_privs_of_role(GetUserId(), ownerId))
751 36 : aclcheck_error(ACLCHECK_NOT_OWNER, get_object_type(classId, objid),
752 36 : NameStr(*(DatumGetName(name))));
753 :
754 : /* User must have CREATE privilege on new namespace */
755 68 : aclresult = object_aclcheck(NamespaceRelationId, nspOid, GetUserId(), ACL_CREATE);
756 68 : if (aclresult != ACLCHECK_OK)
757 0 : aclcheck_error(aclresult, OBJECT_SCHEMA,
758 0 : get_namespace_name(nspOid));
759 : }
760 :
761 : /*
762 : * Check for duplicate name (more friendly than unique-index failure).
763 : * Since this is just a friendliness check, we can just skip it in cases
764 : * where there isn't suitable support.
765 : */
766 142 : if (classId == ProcedureRelationId)
767 : {
768 38 : Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tup);
769 :
770 38 : IsThereFunctionInNamespace(NameStr(proc->proname), proc->pronargs,
771 : &proc->proargtypes, nspOid);
772 : }
773 104 : else if (classId == CollationRelationId)
774 : {
775 4 : Form_pg_collation coll = (Form_pg_collation) GETSTRUCT(tup);
776 :
777 4 : IsThereCollationInNamespace(NameStr(coll->collname), nspOid);
778 : }
779 100 : else if (classId == OperatorClassRelationId)
780 : {
781 12 : Form_pg_opclass opc = (Form_pg_opclass) GETSTRUCT(tup);
782 :
783 12 : IsThereOpClassInNamespace(NameStr(opc->opcname),
784 : opc->opcmethod, nspOid);
785 : }
786 88 : else if (classId == OperatorFamilyRelationId)
787 : {
788 12 : Form_pg_opfamily opf = (Form_pg_opfamily) GETSTRUCT(tup);
789 :
790 12 : IsThereOpFamilyInNamespace(NameStr(opf->opfname),
791 : opf->opfmethod, nspOid);
792 : }
793 144 : else if (nameCacheId >= 0 &&
794 68 : SearchSysCacheExists2(nameCacheId, name,
795 : ObjectIdGetDatum(nspOid)))
796 24 : report_namespace_conflict(classId,
797 24 : NameStr(*(DatumGetName(name))),
798 : nspOid);
799 :
800 : /* Build modified tuple */
801 102 : values = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(Datum));
802 102 : nulls = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool));
803 102 : replaces = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool));
804 102 : values[Anum_namespace - 1] = ObjectIdGetDatum(nspOid);
805 102 : replaces[Anum_namespace - 1] = true;
806 102 : newtup = heap_modify_tuple(tup, RelationGetDescr(rel),
807 : values, nulls, replaces);
808 :
809 : /* Perform actual update */
810 102 : CatalogTupleUpdate(rel, &tup->t_self, newtup);
811 :
812 : /* Release memory */
813 102 : pfree(values);
814 102 : pfree(nulls);
815 102 : pfree(replaces);
816 :
817 : /* update dependency to point to the new schema */
818 102 : if (changeDependencyFor(classId, objid,
819 : NamespaceRelationId, oldNspOid, nspOid) != 1)
820 0 : elog(ERROR, "could not change schema dependency for object %u",
821 : objid);
822 :
823 102 : InvokeObjectPostAlterHook(classId, objid, 0);
824 :
825 102 : return oldNspOid;
826 : }
827 :
828 : /*
829 : * Executes an ALTER OBJECT / OWNER TO statement. Based on the object
830 : * type, the function appropriate to that type is executed.
831 : */
832 : ObjectAddress
833 993 : ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
834 : {
835 993 : Oid newowner = get_rolespec_oid(stmt->newowner, false);
836 :
837 986 : switch (stmt->objectType)
838 : {
839 49 : case OBJECT_DATABASE:
840 49 : return AlterDatabaseOwner(strVal(stmt->object), newowner);
841 :
842 41 : case OBJECT_SCHEMA:
843 41 : return AlterSchemaOwner(strVal(stmt->object), newowner);
844 :
845 70 : case OBJECT_TYPE:
846 : case OBJECT_DOMAIN: /* same as TYPE */
847 70 : return AlterTypeOwner(castNode(List, stmt->object), newowner, stmt->objectType);
848 : break;
849 :
850 13 : case OBJECT_FDW:
851 13 : return AlterForeignDataWrapperOwner(strVal(stmt->object),
852 : newowner);
853 :
854 45 : case OBJECT_FOREIGN_SERVER:
855 45 : return AlterForeignServerOwner(strVal(stmt->object),
856 : newowner);
857 :
858 9 : case OBJECT_EVENT_TRIGGER:
859 9 : return AlterEventTriggerOwner(strVal(stmt->object),
860 : newowner);
861 :
862 26 : case OBJECT_PUBLICATION:
863 26 : return AlterPublicationOwner(strVal(stmt->object),
864 : newowner);
865 :
866 11 : case OBJECT_SUBSCRIPTION:
867 11 : return AlterSubscriptionOwner(strVal(stmt->object),
868 : newowner);
869 :
870 : /* Generic cases */
871 722 : case OBJECT_AGGREGATE:
872 : case OBJECT_COLLATION:
873 : case OBJECT_CONVERSION:
874 : case OBJECT_FUNCTION:
875 : case OBJECT_LANGUAGE:
876 : case OBJECT_LARGEOBJECT:
877 : case OBJECT_OPERATOR:
878 : case OBJECT_OPCLASS:
879 : case OBJECT_OPFAMILY:
880 : case OBJECT_PROCEDURE:
881 : case OBJECT_PROPGRAPH:
882 : case OBJECT_ROUTINE:
883 : case OBJECT_STATISTIC_EXT:
884 : case OBJECT_TABLESPACE:
885 : case OBJECT_TSDICTIONARY:
886 : case OBJECT_TSCONFIGURATION:
887 : {
888 : ObjectAddress address;
889 :
890 722 : if (stmt->relation)
891 : {
892 : Relation relation;
893 :
894 33 : address = get_object_address_rv(stmt->objectType,
895 : stmt->relation,
896 : NIL,
897 : &relation,
898 : AccessExclusiveLock,
899 : false);
900 33 : relation_close(relation, NoLock);
901 : }
902 : else
903 : {
904 689 : address = get_object_address(stmt->objectType,
905 : stmt->object,
906 : NULL,
907 : AccessExclusiveLock,
908 : false);
909 : }
910 :
911 717 : AlterObjectOwner_internal(address.classId, address.objectId,
912 : newowner);
913 :
914 589 : return address;
915 : }
916 : break;
917 :
918 0 : default:
919 0 : elog(ERROR, "unrecognized AlterOwnerStmt type: %d",
920 : (int) stmt->objectType);
921 : return InvalidObjectAddress; /* keep compiler happy */
922 : }
923 : }
924 :
925 : /*
926 : * Generic function to change the ownership of a given object, for simple
927 : * cases (won't work for tables, nor other cases where we need to do more than
928 : * change the ownership column of a single catalog entry).
929 : *
930 : * classId: OID of catalog containing object
931 : * objectId: OID of object to change the ownership of
932 : * new_ownerId: OID of new object owner
933 : *
934 : * This will work on large objects, but we have to beware of the fact that
935 : * classId isn't the OID of the catalog to modify in that case.
936 : */
937 : void
938 729 : AlterObjectOwner_internal(Oid classId, Oid objectId, Oid new_ownerId)
939 : {
940 : /* For large objects, the catalog to modify is pg_largeobject_metadata */
941 729 : Oid catalogId = (classId == LargeObjectRelationId) ? LargeObjectMetadataRelationId : classId;
942 729 : AttrNumber Anum_oid = get_object_attnum_oid(catalogId);
943 729 : AttrNumber Anum_owner = get_object_attnum_owner(catalogId);
944 729 : AttrNumber Anum_namespace = get_object_attnum_namespace(catalogId);
945 729 : AttrNumber Anum_acl = get_object_attnum_acl(catalogId);
946 729 : AttrNumber Anum_name = get_object_attnum_name(catalogId);
947 : Relation rel;
948 : HeapTuple oldtup;
949 : Datum datum;
950 : bool isnull;
951 : Oid old_ownerId;
952 729 : Oid namespaceId = InvalidOid;
953 :
954 729 : rel = table_open(catalogId, RowExclusiveLock);
955 :
956 : /* Search tuple and lock it. */
957 : oldtup =
958 729 : get_catalog_object_by_oid_extended(rel, Anum_oid, objectId, true);
959 729 : if (oldtup == NULL)
960 0 : elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
961 : objectId, RelationGetRelationName(rel));
962 :
963 729 : datum = heap_getattr(oldtup, Anum_owner,
964 : RelationGetDescr(rel), &isnull);
965 : Assert(!isnull);
966 729 : old_ownerId = DatumGetObjectId(datum);
967 :
968 729 : if (Anum_namespace != InvalidAttrNumber)
969 : {
970 629 : datum = heap_getattr(oldtup, Anum_namespace,
971 : RelationGetDescr(rel), &isnull);
972 : Assert(!isnull);
973 629 : namespaceId = DatumGetObjectId(datum);
974 : }
975 :
976 729 : if (old_ownerId != new_ownerId)
977 : {
978 : AttrNumber nattrs;
979 : HeapTuple newtup;
980 : Datum *values;
981 : bool *nulls;
982 : bool *replaces;
983 :
984 : /* Superusers can bypass permission checks */
985 272 : if (!superuser())
986 : {
987 : /* must be owner */
988 172 : if (!has_privs_of_role(GetUserId(), old_ownerId))
989 : {
990 : char *objname;
991 : char namebuf[NAMEDATALEN];
992 :
993 44 : if (Anum_name != InvalidAttrNumber)
994 : {
995 44 : datum = heap_getattr(oldtup, Anum_name,
996 : RelationGetDescr(rel), &isnull);
997 : Assert(!isnull);
998 44 : objname = NameStr(*DatumGetName(datum));
999 : }
1000 : else
1001 : {
1002 0 : snprintf(namebuf, sizeof(namebuf), "%u", objectId);
1003 0 : objname = namebuf;
1004 : }
1005 44 : aclcheck_error(ACLCHECK_NOT_OWNER,
1006 : get_object_type(catalogId, objectId),
1007 : objname);
1008 : }
1009 : /* Must be able to become new owner */
1010 128 : check_can_set_role(GetUserId(), new_ownerId);
1011 :
1012 : /* New owner must have CREATE privilege on namespace */
1013 44 : if (OidIsValid(namespaceId))
1014 : {
1015 : AclResult aclresult;
1016 :
1017 40 : aclresult = object_aclcheck(NamespaceRelationId, namespaceId, new_ownerId,
1018 : ACL_CREATE);
1019 40 : if (aclresult != ACLCHECK_OK)
1020 0 : aclcheck_error(aclresult, OBJECT_SCHEMA,
1021 0 : get_namespace_name(namespaceId));
1022 : }
1023 : }
1024 :
1025 : /* Build a modified tuple */
1026 144 : nattrs = RelationGetNumberOfAttributes(rel);
1027 144 : values = palloc0(nattrs * sizeof(Datum));
1028 144 : nulls = palloc0(nattrs * sizeof(bool));
1029 144 : replaces = palloc0(nattrs * sizeof(bool));
1030 144 : values[Anum_owner - 1] = ObjectIdGetDatum(new_ownerId);
1031 144 : replaces[Anum_owner - 1] = true;
1032 :
1033 : /*
1034 : * Determine the modified ACL for the new owner. This is only
1035 : * necessary when the ACL is non-null.
1036 : */
1037 144 : if (Anum_acl != InvalidAttrNumber)
1038 : {
1039 79 : datum = heap_getattr(oldtup,
1040 : Anum_acl, RelationGetDescr(rel), &isnull);
1041 79 : if (!isnull)
1042 : {
1043 : Acl *newAcl;
1044 :
1045 4 : newAcl = aclnewowner(DatumGetAclP(datum),
1046 : old_ownerId, new_ownerId);
1047 4 : values[Anum_acl - 1] = PointerGetDatum(newAcl);
1048 4 : replaces[Anum_acl - 1] = true;
1049 : }
1050 : }
1051 :
1052 144 : newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel),
1053 : values, nulls, replaces);
1054 :
1055 : /* Perform actual update */
1056 144 : CatalogTupleUpdate(rel, &newtup->t_self, newtup);
1057 :
1058 144 : UnlockTuple(rel, &oldtup->t_self, InplaceUpdateTupleLock);
1059 :
1060 : /* Update owner dependency reference */
1061 144 : changeDependencyOnOwner(classId, objectId, new_ownerId);
1062 :
1063 : /* Release memory */
1064 144 : pfree(values);
1065 144 : pfree(nulls);
1066 144 : pfree(replaces);
1067 : }
1068 : else
1069 457 : UnlockTuple(rel, &oldtup->t_self, InplaceUpdateTupleLock);
1070 :
1071 : /* Note the post-alter hook gets classId not catalogId */
1072 601 : InvokeObjectPostAlterHook(classId, objectId, 0);
1073 :
1074 601 : table_close(rel, RowExclusiveLock);
1075 601 : }
|