LCOV - code coverage report
Current view: top level - src/backend/commands - schemacmds.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 88.2 % 127 112
Test Date: 2026-03-02 08:16:13 Functions: 100.0 % 5 5
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-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          568 : CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
      53              :                     int stmt_location, int stmt_len)
      54              : {
      55          568 :     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          568 :     char       *nsp = namespace_search_path;
      64              :     AclResult   aclresult;
      65              :     ObjectAddress address;
      66              :     StringInfoData pathbuf;
      67              : 
      68          568 :     GetUserIdAndSecContext(&saved_uid, &save_sec_context);
      69              : 
      70              :     /*
      71              :      * Who is supposed to own the new schema?
      72              :      */
      73          568 :     if (stmt->authrole)
      74           88 :         owner_uid = get_rolespec_oid(stmt->authrole, false);
      75              :     else
      76          480 :         owner_uid = saved_uid;
      77              : 
      78              :     /* fill schema name with the user name if not specified */
      79          562 :     if (!schemaName)
      80              :     {
      81              :         HeapTuple   tuple;
      82              : 
      83           36 :         tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(owner_uid));
      84           36 :         if (!HeapTupleIsValid(tuple))
      85            0 :             elog(ERROR, "cache lookup failed for role %u", owner_uid);
      86              :         schemaName =
      87           36 :             pstrdup(NameStr(((Form_pg_authid) GETSTRUCT(tuple))->rolname));
      88           36 :         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          562 :     aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, saved_uid, ACL_CREATE);
      99          562 :     if (aclresult != ACLCHECK_OK)
     100            0 :         aclcheck_error(aclresult, OBJECT_DATABASE,
     101            0 :                        get_database_name(MyDatabaseId));
     102              : 
     103          562 :     check_can_set_role(saved_uid, owner_uid);
     104              : 
     105              :     /* Additional check to protect reserved schema names */
     106          559 :     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          558 :     if (stmt->if_not_exists)
     120              :     {
     121           17 :         namespaceId = get_namespace_oid(schemaName, true);
     122           17 :         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           12 :             ObjectAddressSet(address, NamespaceRelationId, namespaceId);
     129           12 :             checkMembershipInCurrentExtension(&address);
     130              : 
     131              :             /* OK to skip */
     132           11 :             ereport(NOTICE,
     133              :                     (errcode(ERRCODE_DUPLICATE_SCHEMA),
     134              :                      errmsg("schema \"%s\" already exists, skipping",
     135              :                             schemaName)));
     136           11 :             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          546 :     if (saved_uid != owner_uid)
     149           35 :         SetUserIdAndSecContext(owner_uid,
     150              :                                save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
     151              : 
     152              :     /* Create the schema's namespace */
     153          546 :     namespaceId = NamespaceCreate(schemaName, owner_uid, false);
     154              : 
     155              :     /* Advance cmd counter to make the namespace visible */
     156          543 :     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          543 :     save_nestlevel = NewGUCNestLevel();
     166              : 
     167          543 :     initStringInfo(&pathbuf);
     168          543 :     appendStringInfoString(&pathbuf, quote_identifier(schemaName));
     169              : 
     170          546 :     while (scanner_isspace(*nsp))
     171            3 :         nsp++;
     172              : 
     173          543 :     if (*nsp != '\0')
     174          525 :         appendStringInfo(&pathbuf, ", %s", nsp);
     175              : 
     176          543 :     (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          543 :     ObjectAddressSet(address, NamespaceRelationId, namespaceId);
     187          543 :     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          543 :     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          739 :     foreach(parsetree_item, parsetree_list)
     207              :     {
     208          247 :         Node       *stmt = (Node *) lfirst(parsetree_item);
     209              :         PlannedStmt *wrapper;
     210              : 
     211              :         /* need to make a wrapper PlannedStmt */
     212          247 :         wrapper = makeNode(PlannedStmt);
     213          247 :         wrapper->commandType = CMD_UTILITY;
     214          247 :         wrapper->canSetTag = false;
     215          247 :         wrapper->utilityStmt = stmt;
     216          247 :         wrapper->stmt_location = stmt_location;
     217          247 :         wrapper->stmt_len = stmt_len;
     218          247 :         wrapper->planOrigin = PLAN_STMT_INTERNAL;
     219              : 
     220              :         /* do this step */
     221          247 :         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          241 :         CommandCounterIncrement();
     232              :     }
     233              : 
     234              :     /*
     235              :      * Restore the GUC variable search_path we set above.
     236              :      */
     237          492 :     AtEOXact_GUC(true, save_nestlevel);
     238              : 
     239              :     /* Reset current user and security context */
     240          492 :     SetUserIdAndSecContext(saved_uid, save_sec_context);
     241              : 
     242          492 :     return namespaceId;
     243              : }
     244              : 
     245              : 
     246              : /*
     247              :  * Rename schema
     248              :  */
     249              : ObjectAddress
     250           10 : 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           10 :     rel = table_open(NamespaceRelationId, RowExclusiveLock);
     260              : 
     261           10 :     tup = SearchSysCacheCopy1(NAMESPACENAME, CStringGetDatum(oldname));
     262           10 :     if (!HeapTupleIsValid(tup))
     263            0 :         ereport(ERROR,
     264              :                 (errcode(ERRCODE_UNDEFINED_SCHEMA),
     265              :                  errmsg("schema \"%s\" does not exist", oldname)));
     266              : 
     267           10 :     nspform = (Form_pg_namespace) GETSTRUCT(tup);
     268           10 :     nspOid = nspform->oid;
     269              : 
     270              :     /* make sure the new name doesn't exist */
     271           10 :     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           10 :     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           10 :     aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(), ACL_CREATE);
     283           10 :     if (aclresult != ACLCHECK_OK)
     284            0 :         aclcheck_error(aclresult, OBJECT_DATABASE,
     285            0 :                        get_database_name(MyDatabaseId));
     286              : 
     287           10 :     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           10 :     namestrcpy(&nspform->nspname, newname);
     295           10 :     CatalogTupleUpdate(rel, &tup->t_self, tup);
     296              : 
     297           10 :     InvokeObjectPostAlterHook(NamespaceRelationId, nspOid, 0);
     298              : 
     299           10 :     ObjectAddressSet(address, NamespaceRelationId, nspOid);
     300              : 
     301           10 :     table_close(rel, NoLock);
     302           10 :     heap_freetuple(tup);
     303              : 
     304           10 :     return address;
     305              : }
     306              : 
     307              : void
     308            4 : AlterSchemaOwner_oid(Oid schemaoid, Oid newOwnerId)
     309              : {
     310              :     HeapTuple   tup;
     311              :     Relation    rel;
     312              : 
     313            4 :     rel = table_open(NamespaceRelationId, RowExclusiveLock);
     314              : 
     315            4 :     tup = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(schemaoid));
     316            4 :     if (!HeapTupleIsValid(tup))
     317            0 :         elog(ERROR, "cache lookup failed for schema %u", schemaoid);
     318              : 
     319            4 :     AlterSchemaOwner_internal(tup, rel, newOwnerId);
     320              : 
     321            4 :     ReleaseSysCache(tup);
     322              : 
     323            4 :     table_close(rel, RowExclusiveLock);
     324            4 : }
     325              : 
     326              : 
     327              : /*
     328              :  * Change schema owner
     329              :  */
     330              : ObjectAddress
     331           32 : 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           32 :     rel = table_open(NamespaceRelationId, RowExclusiveLock);
     340              : 
     341           32 :     tup = SearchSysCache1(NAMESPACENAME, CStringGetDatum(name));
     342           32 :     if (!HeapTupleIsValid(tup))
     343            0 :         ereport(ERROR,
     344              :                 (errcode(ERRCODE_UNDEFINED_SCHEMA),
     345              :                  errmsg("schema \"%s\" does not exist", name)));
     346              : 
     347           32 :     nspform = (Form_pg_namespace) GETSTRUCT(tup);
     348           32 :     nspOid = nspform->oid;
     349              : 
     350           32 :     AlterSchemaOwner_internal(tup, rel, newOwnerId);
     351              : 
     352           32 :     ObjectAddressSet(address, NamespaceRelationId, nspOid);
     353              : 
     354           32 :     ReleaseSysCache(tup);
     355              : 
     356           32 :     table_close(rel, RowExclusiveLock);
     357              : 
     358           32 :     return address;
     359              : }
     360              : 
     361              : static void
     362           36 : 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           36 :     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           36 :     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           21 :         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           21 :         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           21 :         aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(),
     404              :                                     ACL_CREATE);
     405           21 :         if (aclresult != ACLCHECK_OK)
     406            0 :             aclcheck_error(aclresult, OBJECT_DATABASE,
     407            0 :                            get_database_name(MyDatabaseId));
     408              : 
     409           21 :         memset(repl_null, false, sizeof(repl_null));
     410           21 :         memset(repl_repl, false, sizeof(repl_repl));
     411              : 
     412           21 :         repl_repl[Anum_pg_namespace_nspowner - 1] = true;
     413           21 :         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           21 :         aclDatum = SysCacheGetAttr(NAMESPACENAME, tup,
     420              :                                    Anum_pg_namespace_nspacl,
     421              :                                    &isNull);
     422           21 :         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           21 :         newtuple = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null, repl_repl);
     431              : 
     432           21 :         CatalogTupleUpdate(rel, &newtuple->t_self, newtuple);
     433              : 
     434           21 :         heap_freetuple(newtuple);
     435              : 
     436              :         /* Update owner dependency reference */
     437           21 :         changeDependencyOnOwner(NamespaceRelationId, nspForm->oid,
     438              :                                 newOwnerId);
     439              :     }
     440              : 
     441           36 :     InvokeObjectPostAlterHook(NamespaceRelationId,
     442              :                               nspForm->oid, 0);
     443           36 : }
        

Generated by: LCOV version 2.0-1