LCOV - code coverage report
Current view: top level - src/backend/commands - schemacmds.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 88.2 % 127 112
Test Date: 2026-07-03 19:57:34 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 62.5 % 72 45

             Branch data     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
      52                 :         776 : CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
      53                 :             :                     int stmt_location, int stmt_len)
      54                 :             : {
      55                 :         776 :     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                 :         776 :     char       *nsp = namespace_search_path;
      64                 :             :     AclResult   aclresult;
      65                 :             :     ObjectAddress address;
      66                 :             :     StringInfoData pathbuf;
      67                 :             : 
      68                 :         776 :     GetUserIdAndSecContext(&saved_uid, &save_sec_context);
      69                 :             : 
      70                 :             :     /*
      71                 :             :      * Who is supposed to own the new schema?
      72                 :             :      */
      73         [ +  + ]:         776 :     if (stmt->authrole)
      74                 :         119 :         owner_uid = get_rolespec_oid(stmt->authrole, false);
      75                 :             :     else
      76                 :         657 :         owner_uid = saved_uid;
      77                 :             : 
      78                 :             :     /* fill schema name with the user name if not specified */
      79         [ +  + ]:         770 :     if (!schemaName)
      80                 :             :     {
      81                 :             :         HeapTuple   tuple;
      82                 :             : 
      83                 :          52 :         tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(owner_uid));
      84         [ -  + ]:          52 :         if (!HeapTupleIsValid(tuple))
      85         [ #  # ]:           0 :             elog(ERROR, "cache lookup failed for role %u", owner_uid);
      86                 :             :         schemaName =
      87                 :          52 :             pstrdup(NameStr(((Form_pg_authid) GETSTRUCT(tuple))->rolname));
      88                 :          52 :         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                 :         770 :     aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, saved_uid, ACL_CREATE);
      99         [ -  + ]:         770 :     if (aclresult != ACLCHECK_OK)
     100                 :           0 :         aclcheck_error(aclresult, OBJECT_DATABASE,
     101                 :           0 :                        get_database_name(MyDatabaseId));
     102                 :             : 
     103                 :         770 :     check_can_set_role(saved_uid, owner_uid);
     104                 :             : 
     105                 :             :     /* Additional check to protect reserved schema names */
     106   [ +  +  +  + ]:         766 :     if (!allowSystemTableMods && IsReservedName(schemaName))
     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                 :             :      */
     119         [ +  + ]:         765 :     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                 :             :      */
     148         [ +  + ]:         752 :     if (saved_uid != owner_uid)
     149                 :          49 :         SetUserIdAndSecContext(owner_uid,
     150                 :             :                                save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
     151                 :             : 
     152                 :             :     /* Create the schema's namespace */
     153                 :         752 :     namespaceId = NamespaceCreate(schemaName, owner_uid, false);
     154                 :             : 
     155                 :             :     /* Advance cmd counter to make the namespace visible */
     156                 :         748 :     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                 :         748 :     save_nestlevel = NewGUCNestLevel();
     166                 :             : 
     167                 :         748 :     initStringInfo(&pathbuf);
     168                 :         748 :     appendStringInfoString(&pathbuf, quote_identifier(schemaName));
     169                 :             : 
     170         [ +  + ]:         752 :     while (scanner_isspace(*nsp))
     171                 :           4 :         nsp++;
     172                 :             : 
     173         [ +  + ]:         748 :     if (*nsp != '\0')
     174                 :         725 :         appendStringInfo(&pathbuf, ", %s", nsp);
     175                 :             : 
     176                 :         748 :     (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                 :         748 :     ObjectAddressSet(address, NamespaceRelationId, namespaceId);
     187                 :         748 :     EventTriggerCollectSimpleCommand(address, InvalidObjectAddress,
     188                 :             :                                      (Node *) stmt);
     189                 :             : 
     190                 :             :     /*
     191                 :             :      * Examine the list of commands embedded in the CREATE SCHEMA command, and
     192                 :             :      * do preliminary transformations.  Note that the result is still a list
     193                 :             :      * of raw parsetrees --- we cannot, in general, run parse analysis on one
     194                 :             :      * statement until we have actually executed the prior ones.
     195                 :             :      */
     196                 :         748 :     parsetree_list = transformCreateSchemaStmtElements(pstate,
     197                 :             :                                                        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   [ +  +  +  +  :        1195 :     foreach(parsetree_item, parsetree_list)
                   +  + ]
     207                 :             :     {
     208                 :         523 :         Node       *stmt = (Node *) lfirst(parsetree_item);
     209                 :             :         PlannedStmt *wrapper;
     210                 :             : 
     211                 :             :         /* need to make a wrapper PlannedStmt */
     212                 :         523 :         wrapper = makeNode(PlannedStmt);
     213                 :         523 :         wrapper->commandType = CMD_UTILITY;
     214                 :         523 :         wrapper->canSetTag = false;
     215                 :         523 :         wrapper->utilityStmt = stmt;
     216                 :         523 :         wrapper->stmt_location = stmt_location;
     217                 :         523 :         wrapper->stmt_len = stmt_len;
     218                 :         523 :         wrapper->planOrigin = PLAN_STMT_INTERNAL;
     219                 :             : 
     220                 :             :         /* do this step */
     221                 :         523 :         ProcessUtility(wrapper,
     222                 :             :                        pstate->p_sourcetext,
     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                 :         515 :         CommandCounterIncrement();
     232                 :             :     }
     233                 :             : 
     234                 :             :     /*
     235                 :             :      * Restore the GUC variable search_path we set above.
     236                 :             :      */
     237                 :         672 :     AtEOXact_GUC(true, save_nestlevel);
     238                 :             : 
     239                 :             :     /* Reset current user and security context */
     240                 :         672 :     SetUserIdAndSecContext(saved_uid, save_sec_context);
     241                 :             : 
     242                 :         672 :     return namespaceId;
     243                 :             : }
     244                 :             : 
     245                 :             : 
     246                 :             : /*
     247                 :             :  * Rename schema
     248                 :             :  */
     249                 :             : ObjectAddress
     250                 :          13 : 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                 :          13 :     rel = table_open(NamespaceRelationId, RowExclusiveLock);
     260                 :             : 
     261                 :          13 :     tup = SearchSysCacheCopy1(NAMESPACENAME, CStringGetDatum(oldname));
     262         [ -  + ]:          13 :     if (!HeapTupleIsValid(tup))
     263         [ #  # ]:           0 :         ereport(ERROR,
     264                 :             :                 (errcode(ERRCODE_UNDEFINED_SCHEMA),
     265                 :             :                  errmsg("schema \"%s\" does not exist", oldname)));
     266                 :             : 
     267                 :          13 :     nspform = (Form_pg_namespace) GETSTRUCT(tup);
     268                 :          13 :     nspOid = nspform->oid;
     269                 :             : 
     270                 :             :     /* make sure the new name doesn't exist */
     271         [ -  + ]:          13 :     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         [ -  + ]:          13 :     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                 :          13 :     aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(), ACL_CREATE);
     283         [ -  + ]:          13 :     if (aclresult != ACLCHECK_OK)
     284                 :           0 :         aclcheck_error(aclresult, OBJECT_DATABASE,
     285                 :           0 :                        get_database_name(MyDatabaseId));
     286                 :             : 
     287   [ +  -  -  + ]:          13 :     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                 :          13 :     namestrcpy(&nspform->nspname, newname);
     295                 :          13 :     CatalogTupleUpdate(rel, &tup->t_self, tup);
     296                 :             : 
     297         [ -  + ]:          13 :     InvokeObjectPostAlterHook(NamespaceRelationId, nspOid, 0);
     298                 :             : 
     299                 :          13 :     ObjectAddressSet(address, NamespaceRelationId, nspOid);
     300                 :             : 
     301                 :          13 :     table_close(rel, NoLock);
     302                 :          13 :     heap_freetuple(tup);
     303                 :             : 
     304                 :          13 :     return address;
     305                 :             : }
     306                 :             : 
     307                 :             : void
     308                 :           5 : AlterSchemaOwner_oid(Oid schemaoid, Oid newOwnerId)
     309                 :             : {
     310                 :             :     HeapTuple   tup;
     311                 :             :     Relation    rel;
     312                 :             : 
     313                 :           5 :     rel = table_open(NamespaceRelationId, RowExclusiveLock);
     314                 :             : 
     315                 :           5 :     tup = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(schemaoid));
     316         [ -  + ]:           5 :     if (!HeapTupleIsValid(tup))
     317         [ #  # ]:           0 :         elog(ERROR, "cache lookup failed for schema %u", schemaoid);
     318                 :             : 
     319                 :           5 :     AlterSchemaOwner_internal(tup, rel, newOwnerId);
     320                 :             : 
     321                 :           5 :     ReleaseSysCache(tup);
     322                 :             : 
     323                 :           5 :     table_close(rel, RowExclusiveLock);
     324                 :           5 : }
     325                 :             : 
     326                 :             : 
     327                 :             : /*
     328                 :             :  * Change schema owner
     329                 :             :  */
     330                 :             : ObjectAddress
     331                 :          41 : 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                 :          41 :     rel = table_open(NamespaceRelationId, RowExclusiveLock);
     340                 :             : 
     341                 :          41 :     tup = SearchSysCache1(NAMESPACENAME, CStringGetDatum(name));
     342         [ -  + ]:          41 :     if (!HeapTupleIsValid(tup))
     343         [ #  # ]:           0 :         ereport(ERROR,
     344                 :             :                 (errcode(ERRCODE_UNDEFINED_SCHEMA),
     345                 :             :                  errmsg("schema \"%s\" does not exist", name)));
     346                 :             : 
     347                 :          41 :     nspform = (Form_pg_namespace) GETSTRUCT(tup);
     348                 :          41 :     nspOid = nspform->oid;
     349                 :             : 
     350                 :          41 :     AlterSchemaOwner_internal(tup, rel, newOwnerId);
     351                 :             : 
     352                 :          41 :     ObjectAddressSet(address, NamespaceRelationId, nspOid);
     353                 :             : 
     354                 :          41 :     ReleaseSysCache(tup);
     355                 :             : 
     356                 :          41 :     table_close(rel, RowExclusiveLock);
     357                 :             : 
     358                 :          41 :     return address;
     359                 :             : }
     360                 :             : 
     361                 :             : static void
     362                 :          46 : 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                 :          46 :     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         [ +  + ]:          46 :     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         [ -  + ]:          27 :         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                 :          27 :         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                 :          27 :         aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(),
     404                 :             :                                     ACL_CREATE);
     405         [ -  + ]:          27 :         if (aclresult != ACLCHECK_OK)
     406                 :           0 :             aclcheck_error(aclresult, OBJECT_DATABASE,
     407                 :           0 :                            get_database_name(MyDatabaseId));
     408                 :             : 
     409                 :          27 :         memset(repl_null, false, sizeof(repl_null));
     410                 :          27 :         memset(repl_repl, false, sizeof(repl_repl));
     411                 :             : 
     412                 :          27 :         repl_repl[Anum_pg_namespace_nspowner - 1] = true;
     413                 :          27 :         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                 :          27 :         aclDatum = SysCacheGetAttr(NAMESPACENAME, tup,
     420                 :             :                                    Anum_pg_namespace_nspacl,
     421                 :             :                                    &isNull);
     422         [ +  + ]:          27 :         if (!isNull)
     423                 :             :         {
     424                 :           2 :             newAcl = aclnewowner(DatumGetAclP(aclDatum),
     425                 :             :                                  nspForm->nspowner, newOwnerId);
     426                 :           2 :             repl_repl[Anum_pg_namespace_nspacl - 1] = true;
     427                 :           2 :             repl_val[Anum_pg_namespace_nspacl - 1] = PointerGetDatum(newAcl);
     428                 :             :         }
     429                 :             : 
     430                 :          27 :         newtuple = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null, repl_repl);
     431                 :             : 
     432                 :          27 :         CatalogTupleUpdate(rel, &newtuple->t_self, newtuple);
     433                 :             : 
     434                 :          27 :         heap_freetuple(newtuple);
     435                 :             : 
     436                 :             :         /* Update owner dependency reference */
     437                 :          27 :         changeDependencyOnOwner(NamespaceRelationId, nspForm->oid,
     438                 :             :                                 newOwnerId);
     439                 :             :     }
     440                 :             : 
     441         [ -  + ]:          46 :     InvokeObjectPostAlterHook(NamespaceRelationId,
     442                 :             :                               nspForm->oid, 0);
     443                 :          46 : }
        

Generated by: LCOV version 2.0-1