LCOV - code coverage report
Current view: top level - src/backend/commands - schemacmds.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13beta1 Lines: 103 126 81.7 %
Date: 2020-06-01 10:07:15 Functions: 6 6 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13