Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * schemacmds.c
4 : * schema creation/manipulation commands
5 : *
6 : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/backend/commands/schemacmds.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include "access/htup_details.h"
18 : #include "access/table.h"
19 : #include "access/xact.h"
20 : #include "catalog/catalog.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_authid.h"
26 : #include "catalog/pg_database.h"
27 : #include "catalog/pg_namespace.h"
28 : #include "commands/dbcommands.h"
29 : #include "commands/event_trigger.h"
30 : #include "commands/schemacmds.h"
31 : #include "miscadmin.h"
32 : #include "parser/parse_utilcmd.h"
33 : #include "parser/scansup.h"
34 : #include "tcop/utility.h"
35 : #include "utils/acl.h"
36 : #include "utils/builtins.h"
37 : #include "utils/rel.h"
38 : #include "utils/syscache.h"
39 :
40 : static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId);
41 :
42 : /*
43 : * CREATE SCHEMA
44 : *
45 : * Note: caller should pass in location information for the whole
46 : * CREATE SCHEMA statement, which in turn we pass down as the location
47 : * of the component commands. This comports with our general plan of
48 : * reporting location/len for the whole command even when executing
49 : * a subquery.
50 : */
51 : Oid
52 1090 : CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
53 : int stmt_location, int stmt_len)
54 : {
55 1090 : const char *schemaName = stmt->schemaname;
56 : Oid namespaceId;
57 : List *parsetree_list;
58 : ListCell *parsetree_item;
59 : Oid owner_uid;
60 : Oid saved_uid;
61 : int save_sec_context;
62 : int save_nestlevel;
63 1090 : char *nsp = namespace_search_path;
64 : AclResult aclresult;
65 : ObjectAddress address;
66 : StringInfoData pathbuf;
67 :
68 1090 : GetUserIdAndSecContext(&saved_uid, &save_sec_context);
69 :
70 : /*
71 : * Who is supposed to own the new schema?
72 : */
73 1090 : if (stmt->authrole)
74 176 : owner_uid = get_rolespec_oid(stmt->authrole, false);
75 : else
76 914 : owner_uid = saved_uid;
77 :
78 : /* fill schema name with the user name if not specified */
79 1078 : if (!schemaName)
80 : {
81 : HeapTuple tuple;
82 :
83 72 : tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(owner_uid));
84 72 : if (!HeapTupleIsValid(tuple))
85 0 : elog(ERROR, "cache lookup failed for role %u", owner_uid);
86 : schemaName =
87 72 : pstrdup(NameStr(((Form_pg_authid) GETSTRUCT(tuple))->rolname));
88 72 : ReleaseSysCache(tuple);
89 : }
90 :
91 : /*
92 : * To create a schema, must have schema-create privilege on the current
93 : * database and must be able to become the target role (this does not
94 : * imply that the target role itself must have create-schema privilege).
95 : * The latter provision guards against "giveaway" attacks. Note that a
96 : * superuser will always have both of these privileges a fortiori.
97 : */
98 1078 : aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, saved_uid, ACL_CREATE);
99 1078 : if (aclresult != ACLCHECK_OK)
100 0 : aclcheck_error(aclresult, OBJECT_DATABASE,
101 0 : get_database_name(MyDatabaseId));
102 :
103 1078 : check_can_set_role(saved_uid, owner_uid);
104 :
105 : /* Additional check to protect reserved schema names */
106 1072 : if (!allowSystemTableMods && IsReservedName(schemaName))
107 2 : ereport(ERROR,
108 : (errcode(ERRCODE_RESERVED_NAME),
109 : errmsg("unacceptable schema name \"%s\"", schemaName),
110 : errdetail("The prefix \"pg_\" is reserved for system schemas.")));
111 :
112 : /*
113 : * If if_not_exists was given and the schema already exists, bail out.
114 : * (Note: we needn't check this when not if_not_exists, because
115 : * NamespaceCreate will complain anyway.) We could do this before making
116 : * the permissions checks, but since CREATE TABLE IF NOT EXISTS makes its
117 : * creation-permission check first, we do likewise.
118 : */
119 1070 : if (stmt->if_not_exists)
120 : {
121 34 : namespaceId = get_namespace_oid(schemaName, true);
122 34 : if (OidIsValid(namespaceId))
123 : {
124 : /*
125 : * If we are in an extension script, insist that the pre-existing
126 : * object be a member of the extension, to avoid security risks.
127 : */
128 24 : ObjectAddressSet(address, NamespaceRelationId, namespaceId);
129 24 : checkMembershipInCurrentExtension(&address);
130 :
131 : /* OK to skip */
132 22 : ereport(NOTICE,
133 : (errcode(ERRCODE_DUPLICATE_SCHEMA),
134 : errmsg("schema \"%s\" already exists, skipping",
135 : schemaName)));
136 22 : return InvalidOid;
137 : }
138 : }
139 :
140 : /*
141 : * If the requested authorization is different from the current user,
142 : * temporarily set the current user so that the object(s) will be created
143 : * with the correct ownership.
144 : *
145 : * (The setting will be restored at the end of this routine, or in case of
146 : * error, transaction abort will clean things up.)
147 : */
148 1046 : if (saved_uid != owner_uid)
149 70 : SetUserIdAndSecContext(owner_uid,
150 : save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
151 :
152 : /* Create the schema's namespace */
153 1046 : namespaceId = NamespaceCreate(schemaName, owner_uid, false);
154 :
155 : /* Advance cmd counter to make the namespace visible */
156 1040 : CommandCounterIncrement();
157 :
158 : /*
159 : * Prepend the new schema to the current search path.
160 : *
161 : * We use the equivalent of a function SET option to allow the setting to
162 : * persist for exactly the duration of the schema creation. guc.c also
163 : * takes care of undoing the setting on error.
164 : */
165 1040 : save_nestlevel = NewGUCNestLevel();
166 :
167 1040 : initStringInfo(&pathbuf);
168 1040 : appendStringInfoString(&pathbuf, quote_identifier(schemaName));
169 :
170 1046 : while (scanner_isspace(*nsp))
171 6 : nsp++;
172 :
173 1040 : if (*nsp != '\0')
174 982 : appendStringInfo(&pathbuf, ", %s", nsp);
175 :
176 1040 : (void) set_config_option("search_path", pathbuf.data,
177 : PGC_USERSET, PGC_S_SESSION,
178 : GUC_ACTION_SAVE, true, 0, false);
179 :
180 : /*
181 : * Report the new schema to possibly interested event triggers. Note we
182 : * must do this here and not in ProcessUtilitySlow because otherwise the
183 : * objects created below are reported before the schema, which would be
184 : * wrong.
185 : */
186 1040 : ObjectAddressSet(address, NamespaceRelationId, namespaceId);
187 1040 : EventTriggerCollectSimpleCommand(address, InvalidObjectAddress,
188 : (Node *) stmt);
189 :
190 : /*
191 : * Examine the list of commands embedded in the CREATE SCHEMA command, and
192 : * reorganize them into a sequentially executable order with no forward
193 : * references. Note that the result is still a list of raw parsetrees ---
194 : * we cannot, in general, run parse analysis on one statement until we
195 : * have actually executed the prior ones.
196 : */
197 1040 : parsetree_list = transformCreateSchemaStmtElements(stmt->schemaElts,
198 : schemaName);
199 :
200 : /*
201 : * Execute each command contained in the CREATE SCHEMA. Since the grammar
202 : * allows only utility commands in CREATE SCHEMA, there is no need to pass
203 : * them through parse_analyze_*() or the rewriter; we can just hand them
204 : * straight to ProcessUtility.
205 : */
206 1388 : foreach(parsetree_item, parsetree_list)
207 : {
208 450 : Node *stmt = (Node *) lfirst(parsetree_item);
209 : PlannedStmt *wrapper;
210 :
211 : /* need to make a wrapper PlannedStmt */
212 450 : wrapper = makeNode(PlannedStmt);
213 450 : wrapper->commandType = CMD_UTILITY;
214 450 : wrapper->canSetTag = false;
215 450 : wrapper->utilityStmt = stmt;
216 450 : wrapper->stmt_location = stmt_location;
217 450 : wrapper->stmt_len = stmt_len;
218 450 : wrapper->planOrigin = PLAN_STMT_INTERNAL;
219 :
220 : /* do this step */
221 450 : ProcessUtility(wrapper,
222 : queryString,
223 : false,
224 : PROCESS_UTILITY_SUBCOMMAND,
225 : NULL,
226 : NULL,
227 : None_Receiver,
228 : NULL);
229 :
230 : /* make sure later steps can see the object created here */
231 438 : CommandCounterIncrement();
232 : }
233 :
234 : /*
235 : * Restore the GUC variable search_path we set above.
236 : */
237 938 : AtEOXact_GUC(true, save_nestlevel);
238 :
239 : /* Reset current user and security context */
240 938 : SetUserIdAndSecContext(saved_uid, save_sec_context);
241 :
242 938 : return namespaceId;
243 : }
244 :
245 :
246 : /*
247 : * Rename schema
248 : */
249 : ObjectAddress
250 20 : RenameSchema(const char *oldname, const char *newname)
251 : {
252 : Oid nspOid;
253 : HeapTuple tup;
254 : Relation rel;
255 : AclResult aclresult;
256 : ObjectAddress address;
257 : Form_pg_namespace nspform;
258 :
259 20 : rel = table_open(NamespaceRelationId, RowExclusiveLock);
260 :
261 20 : tup = SearchSysCacheCopy1(NAMESPACENAME, CStringGetDatum(oldname));
262 20 : if (!HeapTupleIsValid(tup))
263 0 : ereport(ERROR,
264 : (errcode(ERRCODE_UNDEFINED_SCHEMA),
265 : errmsg("schema \"%s\" does not exist", oldname)));
266 :
267 20 : nspform = (Form_pg_namespace) GETSTRUCT(tup);
268 20 : nspOid = nspform->oid;
269 :
270 : /* make sure the new name doesn't exist */
271 20 : if (OidIsValid(get_namespace_oid(newname, true)))
272 0 : ereport(ERROR,
273 : (errcode(ERRCODE_DUPLICATE_SCHEMA),
274 : errmsg("schema \"%s\" already exists", newname)));
275 :
276 : /* must be owner */
277 20 : if (!object_ownercheck(NamespaceRelationId, nspOid, GetUserId()))
278 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
279 : oldname);
280 :
281 : /* must have CREATE privilege on database */
282 20 : aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(), ACL_CREATE);
283 20 : if (aclresult != ACLCHECK_OK)
284 0 : aclcheck_error(aclresult, OBJECT_DATABASE,
285 0 : get_database_name(MyDatabaseId));
286 :
287 20 : if (!allowSystemTableMods && IsReservedName(newname))
288 0 : ereport(ERROR,
289 : (errcode(ERRCODE_RESERVED_NAME),
290 : errmsg("unacceptable schema name \"%s\"", newname),
291 : errdetail("The prefix \"pg_\" is reserved for system schemas.")));
292 :
293 : /* rename */
294 20 : namestrcpy(&nspform->nspname, newname);
295 20 : CatalogTupleUpdate(rel, &tup->t_self, tup);
296 :
297 20 : InvokeObjectPostAlterHook(NamespaceRelationId, nspOid, 0);
298 :
299 20 : ObjectAddressSet(address, NamespaceRelationId, nspOid);
300 :
301 20 : table_close(rel, NoLock);
302 20 : heap_freetuple(tup);
303 :
304 20 : return address;
305 : }
306 :
307 : void
308 8 : AlterSchemaOwner_oid(Oid schemaoid, Oid newOwnerId)
309 : {
310 : HeapTuple tup;
311 : Relation rel;
312 :
313 8 : rel = table_open(NamespaceRelationId, RowExclusiveLock);
314 :
315 8 : tup = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(schemaoid));
316 8 : if (!HeapTupleIsValid(tup))
317 0 : elog(ERROR, "cache lookup failed for schema %u", schemaoid);
318 :
319 8 : AlterSchemaOwner_internal(tup, rel, newOwnerId);
320 :
321 8 : ReleaseSysCache(tup);
322 :
323 8 : table_close(rel, RowExclusiveLock);
324 8 : }
325 :
326 :
327 : /*
328 : * Change schema owner
329 : */
330 : ObjectAddress
331 86 : AlterSchemaOwner(const char *name, Oid newOwnerId)
332 : {
333 : Oid nspOid;
334 : HeapTuple tup;
335 : Relation rel;
336 : ObjectAddress address;
337 : Form_pg_namespace nspform;
338 :
339 86 : rel = table_open(NamespaceRelationId, RowExclusiveLock);
340 :
341 86 : tup = SearchSysCache1(NAMESPACENAME, CStringGetDatum(name));
342 86 : if (!HeapTupleIsValid(tup))
343 0 : ereport(ERROR,
344 : (errcode(ERRCODE_UNDEFINED_SCHEMA),
345 : errmsg("schema \"%s\" does not exist", name)));
346 :
347 86 : nspform = (Form_pg_namespace) GETSTRUCT(tup);
348 86 : nspOid = nspform->oid;
349 :
350 86 : AlterSchemaOwner_internal(tup, rel, newOwnerId);
351 :
352 86 : ObjectAddressSet(address, NamespaceRelationId, nspOid);
353 :
354 86 : ReleaseSysCache(tup);
355 :
356 86 : table_close(rel, RowExclusiveLock);
357 :
358 86 : return address;
359 : }
360 :
361 : static void
362 94 : AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId)
363 : {
364 : Form_pg_namespace nspForm;
365 :
366 : Assert(tup->t_tableOid == NamespaceRelationId);
367 : Assert(RelationGetRelid(rel) == NamespaceRelationId);
368 :
369 94 : nspForm = (Form_pg_namespace) GETSTRUCT(tup);
370 :
371 : /*
372 : * If the new owner is the same as the existing owner, consider the
373 : * command to have succeeded. This is for dump restoration purposes.
374 : */
375 94 : if (nspForm->nspowner != newOwnerId)
376 : {
377 : Datum repl_val[Natts_pg_namespace];
378 : bool repl_null[Natts_pg_namespace];
379 : bool repl_repl[Natts_pg_namespace];
380 : Acl *newAcl;
381 : Datum aclDatum;
382 : bool isNull;
383 : HeapTuple newtuple;
384 : AclResult aclresult;
385 :
386 : /* Otherwise, must be owner of the existing object */
387 42 : if (!object_ownercheck(NamespaceRelationId, nspForm->oid, GetUserId()))
388 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
389 0 : NameStr(nspForm->nspname));
390 :
391 : /* Must be able to become new owner */
392 42 : check_can_set_role(GetUserId(), newOwnerId);
393 :
394 : /*
395 : * must have create-schema rights
396 : *
397 : * NOTE: This is different from other alter-owner checks in that the
398 : * current user is checked for create privileges instead of the
399 : * destination owner. This is consistent with the CREATE case for
400 : * schemas. Because superusers will always have this right, we need
401 : * no special case for them.
402 : */
403 42 : aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(),
404 : ACL_CREATE);
405 42 : if (aclresult != ACLCHECK_OK)
406 0 : aclcheck_error(aclresult, OBJECT_DATABASE,
407 0 : get_database_name(MyDatabaseId));
408 :
409 42 : memset(repl_null, false, sizeof(repl_null));
410 42 : memset(repl_repl, false, sizeof(repl_repl));
411 :
412 42 : repl_repl[Anum_pg_namespace_nspowner - 1] = true;
413 42 : repl_val[Anum_pg_namespace_nspowner - 1] = ObjectIdGetDatum(newOwnerId);
414 :
415 : /*
416 : * Determine the modified ACL for the new owner. This is only
417 : * necessary when the ACL is non-null.
418 : */
419 42 : aclDatum = SysCacheGetAttr(NAMESPACENAME, tup,
420 : Anum_pg_namespace_nspacl,
421 : &isNull);
422 42 : if (!isNull)
423 : {
424 4 : newAcl = aclnewowner(DatumGetAclP(aclDatum),
425 : nspForm->nspowner, newOwnerId);
426 4 : repl_repl[Anum_pg_namespace_nspacl - 1] = true;
427 4 : repl_val[Anum_pg_namespace_nspacl - 1] = PointerGetDatum(newAcl);
428 : }
429 :
430 42 : newtuple = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null, repl_repl);
431 :
432 42 : CatalogTupleUpdate(rel, &newtuple->t_self, newtuple);
433 :
434 42 : heap_freetuple(newtuple);
435 :
436 : /* Update owner dependency reference */
437 42 : changeDependencyOnOwner(NamespaceRelationId, nspForm->oid,
438 : newOwnerId);
439 : }
440 :
441 94 : InvokeObjectPostAlterHook(NamespaceRelationId,
442 : nspForm->oid, 0);
443 94 : }
|