Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * schemacmds.c
4 : : * schema creation/manipulation 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/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/event_trigger.h"
29 : : #include "commands/schemacmds.h"
30 : : #include "miscadmin.h"
31 : : #include "parser/parse_utilcmd.h"
32 : : #include "parser/scansup.h"
33 : : #include "tcop/utility.h"
34 : : #include "utils/acl.h"
35 : : #include "utils/builtins.h"
36 : : #include "utils/lsyscache.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
167 tgl@sss.pgh.pa.us 52 :CBC 737 : CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
53 : : int stmt_location, int stmt_len)
54 : : {
4138 bruce@momjian.us 55 : 737 : 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;
1231 noah@leadboat.com 63 : 737 : char *nsp = namespace_search_path;
64 : : AclResult aclresult;
65 : : ObjectAddress address;
66 : : StringInfoData pathbuf;
67 : :
6129 tgl@sss.pgh.pa.us 68 : 737 : GetUserIdAndSecContext(&saved_uid, &save_sec_context);
69 : :
70 : : /*
71 : : * Who is supposed to own the new schema?
72 : : */
4213 alvherre@alvh.no-ip. 73 [ + + ]: 737 : if (stmt->authrole)
74 : 111 : owner_uid = get_rolespec_oid(stmt->authrole, false);
75 : : else
7754 tgl@sss.pgh.pa.us 76 : 626 : owner_uid = saved_uid;
77 : :
78 : : /* fill schema name with the user name if not specified */
4213 alvherre@alvh.no-ip. 79 [ + + ]: 731 : if (!schemaName)
80 : : {
81 : : HeapTuple tuple;
82 : :
83 : 48 : tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(owner_uid));
84 [ - + ]: 48 : if (!HeapTupleIsValid(tuple))
4213 alvherre@alvh.no-ip. 85 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for role %u", owner_uid);
86 : : schemaName =
4213 alvherre@alvh.no-ip. 87 :CBC 48 : pstrdup(NameStr(((Form_pg_authid) GETSTRUCT(tuple))->rolname));
88 : 48 : 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 : : */
1407 peter@eisentraut.org 98 : 731 : aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, saved_uid, ACL_CREATE);
8912 tgl@sss.pgh.pa.us 99 [ - + ]: 731 : if (aclresult != ACLCHECK_OK)
3214 peter_e@gmx.net 100 :UBC 0 : aclcheck_error(aclresult, OBJECT_DATABASE,
8451 tgl@sss.pgh.pa.us 101 : 0 : get_database_name(MyDatabaseId));
102 : :
1402 rhaas@postgresql.org 103 :CBC 731 : check_can_set_role(saved_uid, owner_uid);
104 : :
105 : : /* Additional check to protect reserved schema names */
8924 tgl@sss.pgh.pa.us 106 [ + + + + ]: 727 : if (!allowSystemTableMods && IsReservedName(schemaName))
8465 107 [ + - ]: 1 : 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 : : */
1504 119 [ + + ]: 726 : if (stmt->if_not_exists)
120 : : {
121 : 18 : namespaceId = get_namespace_oid(schemaName, true);
122 [ + + ]: 18 : 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 : 13 : ObjectAddressSet(address, NamespaceRelationId, namespaceId);
129 : 13 : checkMembershipInCurrentExtension(&address);
130 : :
131 : : /* OK to skip */
132 [ + + ]: 12 : ereport(NOTICE,
133 : : (errcode(ERRCODE_DUPLICATE_SCHEMA),
134 : : errmsg("schema \"%s\" already exists, skipping",
135 : : schemaName)));
136 : 12 : 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 : : */
7738 148 [ + + ]: 713 : if (saved_uid != owner_uid)
6129 149 : 45 : SetUserIdAndSecContext(owner_uid,
150 : : save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
151 : :
152 : : /* Create the schema's namespace */
5309 153 : 713 : namespaceId = NamespaceCreate(schemaName, owner_uid, false);
154 : :
155 : : /* Advance cmd counter to make the namespace visible */
8924 156 : 709 : 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 : : */
1231 noah@leadboat.com 165 : 709 : save_nestlevel = NewGUCNestLevel();
166 : :
167 : 709 : initStringInfo(&pathbuf);
168 : 709 : appendStringInfoString(&pathbuf, quote_identifier(schemaName));
169 : :
170 [ + + ]: 713 : while (scanner_isspace(*nsp))
171 : 4 : nsp++;
172 : :
173 [ + + ]: 709 : if (*nsp != '\0')
174 : 690 : appendStringInfo(&pathbuf, ", %s", nsp);
175 : :
176 : 709 : (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 : : */
4150 alvherre@alvh.no-ip. 186 : 709 : ObjectAddressSet(address, NamespaceRelationId, namespaceId);
187 : 709 : 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 : : */
167 tgl@sss.pgh.pa.us 197 : 709 : parsetree_list = transformCreateSchemaStmtElements(pstate,
198 : : stmt->schemaElts,
199 : : schemaName);
200 : :
201 : : /*
202 : : * Execute each command contained in the CREATE SCHEMA. Since the grammar
203 : : * allows only utility commands in CREATE SCHEMA, there is no need to pass
204 : : * them through parse_analyze_*() or the rewriter; we can just hand them
205 : : * straight to ProcessUtility.
206 : : */
8924 207 [ + + + + : 1075 : foreach(parsetree_item, parsetree_list)
+ + ]
208 : : {
12 peter@eisentraut.org 209 :GNC 434 : Node *node = (Node *) lfirst(parsetree_item);
210 : : PlannedStmt *wrapper;
211 : :
212 : : /* need to make a wrapper PlannedStmt */
3536 tgl@sss.pgh.pa.us 213 :CBC 434 : wrapper = makeNode(PlannedStmt);
214 : 434 : wrapper->commandType = CMD_UTILITY;
215 : 434 : wrapper->canSetTag = false;
12 peter@eisentraut.org 216 :GNC 434 : wrapper->utilityStmt = node;
3536 tgl@sss.pgh.pa.us 217 :CBC 434 : wrapper->stmt_location = stmt_location;
218 : 434 : wrapper->stmt_len = stmt_len;
416 michael@paquier.xyz 219 : 434 : wrapper->planOrigin = PLAN_STMT_INTERNAL;
220 : :
221 : : /* do this step */
3536 tgl@sss.pgh.pa.us 222 : 434 : ProcessUtility(wrapper,
223 : : pstate->p_sourcetext,
224 : : false,
225 : : PROCESS_UTILITY_SUBCOMMAND,
226 : : NULL,
227 : : NULL,
228 : : None_Receiver,
229 : : NULL);
230 : :
231 : : /* make sure later steps can see the object created here */
7029 232 : 430 : CommandCounterIncrement();
233 : : }
234 : :
235 : : /*
236 : : * Restore the GUC variable search_path we set above.
237 : : */
1231 noah@leadboat.com 238 : 641 : AtEOXact_GUC(true, save_nestlevel);
239 : :
240 : : /* Reset current user and security context */
6129 tgl@sss.pgh.pa.us 241 : 641 : SetUserIdAndSecContext(saved_uid, save_sec_context);
242 : :
5019 rhaas@postgresql.org 243 : 641 : return namespaceId;
244 : : }
245 : :
246 : :
247 : : /*
248 : : * Rename schema
249 : : */
250 : : ObjectAddress
8486 peter_e@gmx.net 251 : 13 : RenameSchema(const char *oldname, const char *newname)
252 : : {
253 : : Oid nspOid;
254 : : HeapTuple tup;
255 : : Relation rel;
256 : : AclResult aclresult;
257 : : ObjectAddress address;
258 : : Form_pg_namespace nspform;
259 : :
2799 andres@anarazel.de 260 : 13 : rel = table_open(NamespaceRelationId, RowExclusiveLock);
261 : :
6062 rhaas@postgresql.org 262 : 13 : tup = SearchSysCacheCopy1(NAMESPACENAME, CStringGetDatum(oldname));
8486 peter_e@gmx.net 263 [ - + ]: 13 : if (!HeapTupleIsValid(tup))
8486 peter_e@gmx.net 264 [ # # ]:UBC 0 : ereport(ERROR,
265 : : (errcode(ERRCODE_UNDEFINED_SCHEMA),
266 : : errmsg("schema \"%s\" does not exist", oldname)));
267 : :
2861 andres@anarazel.de 268 :CBC 13 : nspform = (Form_pg_namespace) GETSTRUCT(tup);
269 : 13 : nspOid = nspform->oid;
270 : :
271 : : /* make sure the new name doesn't exist */
5890 rhaas@postgresql.org 272 [ - + ]: 13 : if (OidIsValid(get_namespace_oid(newname, true)))
8486 peter_e@gmx.net 273 [ # # ]:UBC 0 : ereport(ERROR,
274 : : (errcode(ERRCODE_DUPLICATE_SCHEMA),
275 : : errmsg("schema \"%s\" already exists", newname)));
276 : :
277 : : /* must be owner */
1407 peter@eisentraut.org 278 [ - + ]:CBC 13 : if (!object_ownercheck(NamespaceRelationId, nspOid, GetUserId()))
3214 peter_e@gmx.net 279 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
280 : : oldname);
281 : :
282 : : /* must have CREATE privilege on database */
1407 peter@eisentraut.org 283 :CBC 13 : aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(), ACL_CREATE);
8486 peter_e@gmx.net 284 [ - + ]: 13 : if (aclresult != ACLCHECK_OK)
3214 peter_e@gmx.net 285 :UBC 0 : aclcheck_error(aclresult, OBJECT_DATABASE,
8451 tgl@sss.pgh.pa.us 286 : 0 : get_database_name(MyDatabaseId));
287 : :
8486 peter_e@gmx.net 288 [ + - - + ]:CBC 13 : if (!allowSystemTableMods && IsReservedName(newname))
8465 tgl@sss.pgh.pa.us 289 [ # # ]:UBC 0 : ereport(ERROR,
290 : : (errcode(ERRCODE_RESERVED_NAME),
291 : : errmsg("unacceptable schema name \"%s\"", newname),
292 : : errdetail("The prefix \"pg_\" is reserved for system schemas.")));
293 : :
294 : : /* rename */
2861 andres@anarazel.de 295 :CBC 13 : namestrcpy(&nspform->nspname, newname);
3519 alvherre@alvh.no-ip. 296 : 13 : CatalogTupleUpdate(rel, &tup->t_self, tup);
297 : :
2861 andres@anarazel.de 298 [ - + ]: 13 : InvokeObjectPostAlterHook(NamespaceRelationId, nspOid, 0);
299 : :
4219 alvherre@alvh.no-ip. 300 : 13 : ObjectAddressSet(address, NamespaceRelationId, nspOid);
301 : :
2799 andres@anarazel.de 302 : 13 : table_close(rel, NoLock);
8486 peter_e@gmx.net 303 : 13 : heap_freetuple(tup);
304 : :
4219 alvherre@alvh.no-ip. 305 : 13 : return address;
306 : : }
307 : :
308 : : void
1461 pg@bowt.ie 309 : 5 : AlterSchemaOwner_oid(Oid schemaoid, Oid newOwnerId)
310 : : {
311 : : HeapTuple tup;
312 : : Relation rel;
313 : :
2799 andres@anarazel.de 314 : 5 : rel = table_open(NamespaceRelationId, RowExclusiveLock);
315 : :
1461 pg@bowt.ie 316 : 5 : tup = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(schemaoid));
7608 alvherre@alvh.no-ip. 317 [ - + ]: 5 : if (!HeapTupleIsValid(tup))
1461 pg@bowt.ie 318 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for schema %u", schemaoid);
319 : :
7608 alvherre@alvh.no-ip. 320 :CBC 5 : AlterSchemaOwner_internal(tup, rel, newOwnerId);
321 : :
322 : 5 : ReleaseSysCache(tup);
323 : :
2799 andres@anarazel.de 324 : 5 : table_close(rel, RowExclusiveLock);
7608 alvherre@alvh.no-ip. 325 : 5 : }
326 : :
327 : :
328 : : /*
329 : : * Change schema owner
330 : : */
331 : : ObjectAddress
7754 tgl@sss.pgh.pa.us 332 : 37 : AlterSchemaOwner(const char *name, Oid newOwnerId)
333 : : {
334 : : Oid nspOid;
335 : : HeapTuple tup;
336 : : Relation rel;
337 : : ObjectAddress address;
338 : : Form_pg_namespace nspform;
339 : :
2799 andres@anarazel.de 340 : 37 : rel = table_open(NamespaceRelationId, RowExclusiveLock);
341 : :
6062 rhaas@postgresql.org 342 : 37 : tup = SearchSysCache1(NAMESPACENAME, CStringGetDatum(name));
8122 tgl@sss.pgh.pa.us 343 [ - + ]: 37 : if (!HeapTupleIsValid(tup))
8122 tgl@sss.pgh.pa.us 344 [ # # ]:UBC 0 : ereport(ERROR,
345 : : (errcode(ERRCODE_UNDEFINED_SCHEMA),
346 : : errmsg("schema \"%s\" does not exist", name)));
347 : :
2861 andres@anarazel.de 348 :CBC 37 : nspform = (Form_pg_namespace) GETSTRUCT(tup);
349 : 37 : nspOid = nspform->oid;
350 : :
7608 alvherre@alvh.no-ip. 351 : 37 : AlterSchemaOwner_internal(tup, rel, newOwnerId);
352 : :
4219 353 : 37 : ObjectAddressSet(address, NamespaceRelationId, nspOid);
354 : :
7608 355 : 37 : ReleaseSysCache(tup);
356 : :
2799 andres@anarazel.de 357 : 37 : table_close(rel, RowExclusiveLock);
358 : :
4219 alvherre@alvh.no-ip. 359 : 37 : return address;
360 : : }
361 : :
362 : : static void
7608 363 : 42 : AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId)
364 : : {
365 : : Form_pg_namespace nspForm;
366 : :
367 [ - + ]: 42 : Assert(tup->t_tableOid == NamespaceRelationId);
368 [ - + ]: 42 : Assert(RelationGetRelid(rel) == NamespaceRelationId);
369 : :
8122 tgl@sss.pgh.pa.us 370 : 42 : nspForm = (Form_pg_namespace) GETSTRUCT(tup);
371 : :
372 : : /*
373 : : * If the new owner is the same as the existing owner, consider the
374 : : * command to have succeeded. This is for dump restoration purposes.
375 : : */
7754 376 [ + + ]: 42 : if (nspForm->nspowner != newOwnerId)
377 : : {
378 : : Datum repl_val[Natts_pg_namespace];
379 : : bool repl_null[Natts_pg_namespace];
380 : : bool repl_repl[Natts_pg_namespace];
381 : : Acl *newAcl;
382 : : Datum aclDatum;
383 : : bool isNull;
384 : : HeapTuple newtuple;
385 : : AclResult aclresult;
386 : :
387 : : /* Otherwise, must be owner of the existing object */
1407 peter@eisentraut.org 388 [ - + ]: 27 : if (!object_ownercheck(NamespaceRelationId, nspForm->oid, GetUserId()))
3214 peter_e@gmx.net 389 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
7608 alvherre@alvh.no-ip. 390 : 0 : NameStr(nspForm->nspname));
391 : :
392 : : /* Must be able to become new owner */
1402 rhaas@postgresql.org 393 :CBC 27 : check_can_set_role(GetUserId(), newOwnerId);
394 : :
395 : : /*
396 : : * must have create-schema rights
397 : : *
398 : : * NOTE: This is different from other alter-owner checks in that the
399 : : * current user is checked for create privileges instead of the
400 : : * destination owner. This is consistent with the CREATE case for
401 : : * schemas. Because superusers will always have this right, we need
402 : : * no special case for them.
403 : : */
1407 peter@eisentraut.org 404 : 27 : aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(),
405 : : ACL_CREATE);
7738 tgl@sss.pgh.pa.us 406 [ - + ]: 27 : if (aclresult != ACLCHECK_OK)
3214 peter_e@gmx.net 407 :UBC 0 : aclcheck_error(aclresult, OBJECT_DATABASE,
7738 tgl@sss.pgh.pa.us 408 : 0 : get_database_name(MyDatabaseId));
409 : :
6531 tgl@sss.pgh.pa.us 410 :CBC 27 : memset(repl_null, false, sizeof(repl_null));
411 : 27 : memset(repl_repl, false, sizeof(repl_repl));
412 : :
413 : 27 : repl_repl[Anum_pg_namespace_nspowner - 1] = true;
7754 414 : 27 : repl_val[Anum_pg_namespace_nspowner - 1] = ObjectIdGetDatum(newOwnerId);
415 : :
416 : : /*
417 : : * Determine the modified ACL for the new owner. This is only
418 : : * necessary when the ACL is non-null.
419 : : */
8085 420 : 27 : aclDatum = SysCacheGetAttr(NAMESPACENAME, tup,
421 : : Anum_pg_namespace_nspacl,
422 : : &isNull);
423 [ + + ]: 27 : if (!isNull)
424 : : {
425 : 2 : newAcl = aclnewowner(DatumGetAclP(aclDatum),
426 : : nspForm->nspowner, newOwnerId);
6531 427 : 2 : repl_repl[Anum_pg_namespace_nspacl - 1] = true;
8085 428 : 2 : repl_val[Anum_pg_namespace_nspacl - 1] = PointerGetDatum(newAcl);
429 : : }
430 : :
6531 431 : 27 : newtuple = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null, repl_repl);
432 : :
3519 alvherre@alvh.no-ip. 433 : 27 : CatalogTupleUpdate(rel, &newtuple->t_self, newtuple);
434 : :
8085 tgl@sss.pgh.pa.us 435 : 27 : heap_freetuple(newtuple);
436 : :
437 : : /* Update owner dependency reference */
2861 andres@anarazel.de 438 : 27 : changeDependencyOnOwner(NamespaceRelationId, nspForm->oid,
439 : : newOwnerId);
440 : : }
441 : :
4935 rhaas@postgresql.org 442 [ - + ]: 42 : InvokeObjectPostAlterHook(NamespaceRelationId,
443 : : nspForm->oid, 0);
8122 tgl@sss.pgh.pa.us 444 : 42 : }
|