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