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