LCOV - code coverage report
Current view: top level - src/backend/commands - comment.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 96.2 % 130 125
Test Date: 2026-03-10 15:14:48 Functions: 100.0 % 6 6
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * comment.c
       4              :  *
       5              :  * PostgreSQL object comments utility code.
       6              :  *
       7              :  * Copyright (c) 1996-2026, PostgreSQL Global Development Group
       8              :  *
       9              :  * IDENTIFICATION
      10              :  *    src/backend/commands/comment.c
      11              :  *
      12              :  *-------------------------------------------------------------------------
      13              :  */
      14              : 
      15              : #include "postgres.h"
      16              : 
      17              : #include "access/genam.h"
      18              : #include "access/htup_details.h"
      19              : #include "access/relation.h"
      20              : #include "access/table.h"
      21              : #include "catalog/indexing.h"
      22              : #include "catalog/objectaddress.h"
      23              : #include "catalog/pg_database.h"
      24              : #include "catalog/pg_description.h"
      25              : #include "catalog/pg_shdescription.h"
      26              : #include "commands/comment.h"
      27              : #include "miscadmin.h"
      28              : #include "utils/builtins.h"
      29              : #include "utils/fmgroids.h"
      30              : #include "utils/rel.h"
      31              : 
      32              : 
      33              : /*
      34              :  * CommentObject --
      35              :  *
      36              :  * This routine is used to add the associated comment into
      37              :  * pg_description for the object specified by the given SQL command.
      38              :  */
      39              : ObjectAddress
      40         3938 : CommentObject(CommentStmt *stmt)
      41              : {
      42              :     Relation    relation;
      43         3938 :     ObjectAddress address = InvalidObjectAddress;
      44              :     bool        missing_ok;
      45              : 
      46              :     /*
      47              :      * When loading a dump, we may see a COMMENT ON DATABASE for the old name
      48              :      * of the database.  Erroring out would prevent pg_restore from completing
      49              :      * (which is really pg_restore's fault, but for now we will work around
      50              :      * the problem here).  Consensus is that the best fix is to treat wrong
      51              :      * database name as a WARNING not an ERROR; hence, the following special
      52              :      * case.
      53              :      */
      54         3938 :     if (stmt->objtype == OBJECT_DATABASE)
      55              :     {
      56          122 :         char       *database = strVal(stmt->object);
      57              : 
      58          122 :         if (!OidIsValid(get_database_oid(database, true)))
      59              :         {
      60            0 :             ereport(WARNING,
      61              :                     (errcode(ERRCODE_UNDEFINED_DATABASE),
      62              :                      errmsg("database \"%s\" does not exist", database)));
      63            0 :             return address;
      64              :         }
      65              :     }
      66              : 
      67              :     /*
      68              :      * During binary upgrade, allow nonexistent large objects so that we don't
      69              :      * have to create them during schema restoration.  pg_upgrade will
      70              :      * transfer the contents of pg_largeobject_metadata via COPY or by
      71              :      * copying/linking its files from the old cluster later on.
      72              :      */
      73         3938 :     missing_ok = IsBinaryUpgrade && stmt->objtype == OBJECT_LARGEOBJECT;
      74              : 
      75              :     /*
      76              :      * Translate the parser representation that identifies this object into an
      77              :      * ObjectAddress.  get_object_address() will throw an error if the object
      78              :      * does not exist, and will also acquire a lock on the target to guard
      79              :      * against concurrent DROP operations.
      80              :      */
      81         3938 :     address = get_object_address(stmt->objtype, stmt->object,
      82              :                                  &relation, ShareUpdateExclusiveLock,
      83              :                                  missing_ok);
      84              : 
      85              :     /* Require ownership of the target object. */
      86         3871 :     check_object_ownership(GetUserId(), stmt->objtype, address,
      87              :                            stmt->object, relation);
      88              : 
      89              :     /* Perform other integrity checks as needed. */
      90         3859 :     switch (stmt->objtype)
      91              :     {
      92          105 :         case OBJECT_COLUMN:
      93              : 
      94              :             /*
      95              :              * Allow comments only on columns of tables, views, materialized
      96              :              * views, composite types, and foreign tables (which are the only
      97              :              * relkinds for which pg_dump will dump per-column comments).  In
      98              :              * particular we wish to disallow comments on index columns,
      99              :              * because the naming of an index's columns may change across PG
     100              :              * versions, so dumping per-column comments could create reload
     101              :              * failures.
     102              :              */
     103          105 :             if (relation->rd_rel->relkind != RELKIND_RELATION &&
     104           28 :                 relation->rd_rel->relkind != RELKIND_VIEW &&
     105           28 :                 relation->rd_rel->relkind != RELKIND_MATVIEW &&
     106           28 :                 relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
     107           21 :                 relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
     108            9 :                 relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
     109            0 :                 ereport(ERROR,
     110              :                         (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     111              :                          errmsg("cannot set comment on relation \"%s\"",
     112              :                                 RelationGetRelationName(relation)),
     113              :                          errdetail_relkind_not_supported(relation->rd_rel->relkind)));
     114          105 :             break;
     115         3754 :         default:
     116         3754 :             break;
     117              :     }
     118              : 
     119              :     /*
     120              :      * Databases, tablespaces, and roles are cluster-wide objects, so any
     121              :      * comments on those objects are recorded in the shared pg_shdescription
     122              :      * catalog.  Comments on all other objects are recorded in pg_description.
     123              :      */
     124         3859 :     if (stmt->objtype == OBJECT_DATABASE || stmt->objtype == OBJECT_TABLESPACE
     125         3737 :         || stmt->objtype == OBJECT_ROLE)
     126          135 :         CreateSharedComments(address.objectId, address.classId, stmt->comment);
     127              :     else
     128         3724 :         CreateComments(address.objectId, address.classId, address.objectSubId,
     129         3724 :                        stmt->comment);
     130              : 
     131              :     /*
     132              :      * If get_object_address() opened the relation for us, we close it to keep
     133              :      * the reference count correct - but we retain any locks acquired by
     134              :      * get_object_address() until commit time, to guard against concurrent
     135              :      * activity.
     136              :      */
     137         3859 :     if (relation != NULL)
     138          322 :         relation_close(relation, NoLock);
     139              : 
     140         3859 :     return address;
     141              : }
     142              : 
     143              : /*
     144              :  * CreateComments --
     145              :  *
     146              :  * Create a comment for the specified object descriptor.  Inserts a new
     147              :  * pg_description tuple, or replaces an existing one with the same key.
     148              :  *
     149              :  * If the comment given is null or an empty string, instead delete any
     150              :  * existing comment for the specified key.
     151              :  */
     152              : void
     153        45881 : CreateComments(Oid oid, Oid classoid, int32 subid, const char *comment)
     154              : {
     155              :     Relation    description;
     156              :     ScanKeyData skey[3];
     157              :     SysScanDesc sd;
     158              :     HeapTuple   oldtuple;
     159        45881 :     HeapTuple   newtuple = NULL;
     160              :     Datum       values[Natts_pg_description];
     161              :     bool        nulls[Natts_pg_description];
     162              :     bool        replaces[Natts_pg_description];
     163              :     int         i;
     164              : 
     165              :     /* Reduce empty-string to NULL case */
     166        45881 :     if (comment != NULL && strlen(comment) == 0)
     167            3 :         comment = NULL;
     168              : 
     169              :     /* Prepare to form or update a tuple, if necessary */
     170        45881 :     if (comment != NULL)
     171              :     {
     172       229145 :         for (i = 0; i < Natts_pg_description; i++)
     173              :         {
     174       183316 :             nulls[i] = false;
     175       183316 :             replaces[i] = true;
     176              :         }
     177        45829 :         values[Anum_pg_description_objoid - 1] = ObjectIdGetDatum(oid);
     178        45829 :         values[Anum_pg_description_classoid - 1] = ObjectIdGetDatum(classoid);
     179        45829 :         values[Anum_pg_description_objsubid - 1] = Int32GetDatum(subid);
     180        45829 :         values[Anum_pg_description_description - 1] = CStringGetTextDatum(comment);
     181              :     }
     182              : 
     183              :     /* Use the index to search for a matching old tuple */
     184              : 
     185        45881 :     ScanKeyInit(&skey[0],
     186              :                 Anum_pg_description_objoid,
     187              :                 BTEqualStrategyNumber, F_OIDEQ,
     188              :                 ObjectIdGetDatum(oid));
     189        45881 :     ScanKeyInit(&skey[1],
     190              :                 Anum_pg_description_classoid,
     191              :                 BTEqualStrategyNumber, F_OIDEQ,
     192              :                 ObjectIdGetDatum(classoid));
     193        45881 :     ScanKeyInit(&skey[2],
     194              :                 Anum_pg_description_objsubid,
     195              :                 BTEqualStrategyNumber, F_INT4EQ,
     196              :                 Int32GetDatum(subid));
     197              : 
     198        45881 :     description = table_open(DescriptionRelationId, RowExclusiveLock);
     199              : 
     200        45881 :     sd = systable_beginscan(description, DescriptionObjIndexId, true,
     201              :                             NULL, 3, skey);
     202              : 
     203        45881 :     while ((oldtuple = systable_getnext(sd)) != NULL)
     204              :     {
     205              :         /* Found the old tuple, so delete or update it */
     206              : 
     207           89 :         if (comment == NULL)
     208           52 :             CatalogTupleDelete(description, &oldtuple->t_self);
     209              :         else
     210              :         {
     211           37 :             newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(description), values,
     212              :                                          nulls, replaces);
     213           37 :             CatalogTupleUpdate(description, &oldtuple->t_self, newtuple);
     214              :         }
     215              : 
     216           89 :         break;                  /* Assume there can be only one match */
     217              :     }
     218              : 
     219        45881 :     systable_endscan(sd);
     220              : 
     221              :     /* If we didn't find an old tuple, insert a new one */
     222              : 
     223        45881 :     if (newtuple == NULL && comment != NULL)
     224              :     {
     225        45792 :         newtuple = heap_form_tuple(RelationGetDescr(description),
     226              :                                    values, nulls);
     227        45792 :         CatalogTupleInsert(description, newtuple);
     228              :     }
     229              : 
     230        45881 :     if (newtuple != NULL)
     231        45829 :         heap_freetuple(newtuple);
     232              : 
     233              :     /* Done */
     234              : 
     235        45881 :     table_close(description, NoLock);
     236        45881 : }
     237              : 
     238              : /*
     239              :  * CreateSharedComments --
     240              :  *
     241              :  * Create a comment for the specified shared object descriptor.  Inserts a
     242              :  * new pg_shdescription tuple, or replaces an existing one with the same key.
     243              :  *
     244              :  * If the comment given is null or an empty string, instead delete any
     245              :  * existing comment for the specified key.
     246              :  */
     247              : void
     248          135 : CreateSharedComments(Oid oid, Oid classoid, const char *comment)
     249              : {
     250              :     Relation    shdescription;
     251              :     ScanKeyData skey[2];
     252              :     SysScanDesc sd;
     253              :     HeapTuple   oldtuple;
     254          135 :     HeapTuple   newtuple = NULL;
     255              :     Datum       values[Natts_pg_shdescription];
     256              :     bool        nulls[Natts_pg_shdescription];
     257              :     bool        replaces[Natts_pg_shdescription];
     258              :     int         i;
     259              : 
     260              :     /* Reduce empty-string to NULL case */
     261          135 :     if (comment != NULL && strlen(comment) == 0)
     262            3 :         comment = NULL;
     263              : 
     264              :     /* Prepare to form or update a tuple, if necessary */
     265          135 :     if (comment != NULL)
     266              :     {
     267          504 :         for (i = 0; i < Natts_pg_shdescription; i++)
     268              :         {
     269          378 :             nulls[i] = false;
     270          378 :             replaces[i] = true;
     271              :         }
     272          126 :         values[Anum_pg_shdescription_objoid - 1] = ObjectIdGetDatum(oid);
     273          126 :         values[Anum_pg_shdescription_classoid - 1] = ObjectIdGetDatum(classoid);
     274          126 :         values[Anum_pg_shdescription_description - 1] = CStringGetTextDatum(comment);
     275              :     }
     276              : 
     277              :     /* Use the index to search for a matching old tuple */
     278              : 
     279          135 :     ScanKeyInit(&skey[0],
     280              :                 Anum_pg_shdescription_objoid,
     281              :                 BTEqualStrategyNumber, F_OIDEQ,
     282              :                 ObjectIdGetDatum(oid));
     283          135 :     ScanKeyInit(&skey[1],
     284              :                 Anum_pg_shdescription_classoid,
     285              :                 BTEqualStrategyNumber, F_OIDEQ,
     286              :                 ObjectIdGetDatum(classoid));
     287              : 
     288          135 :     shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
     289              : 
     290          135 :     sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
     291              :                             NULL, 2, skey);
     292              : 
     293          135 :     while ((oldtuple = systable_getnext(sd)) != NULL)
     294              :     {
     295              :         /* Found the old tuple, so delete or update it */
     296              : 
     297            9 :         if (comment == NULL)
     298            9 :             CatalogTupleDelete(shdescription, &oldtuple->t_self);
     299              :         else
     300              :         {
     301            0 :             newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(shdescription),
     302              :                                          values, nulls, replaces);
     303            0 :             CatalogTupleUpdate(shdescription, &oldtuple->t_self, newtuple);
     304              :         }
     305              : 
     306            9 :         break;                  /* Assume there can be only one match */
     307              :     }
     308              : 
     309          135 :     systable_endscan(sd);
     310              : 
     311              :     /* If we didn't find an old tuple, insert a new one */
     312              : 
     313          135 :     if (newtuple == NULL && comment != NULL)
     314              :     {
     315          126 :         newtuple = heap_form_tuple(RelationGetDescr(shdescription),
     316              :                                    values, nulls);
     317          126 :         CatalogTupleInsert(shdescription, newtuple);
     318              :     }
     319              : 
     320          135 :     if (newtuple != NULL)
     321          126 :         heap_freetuple(newtuple);
     322              : 
     323              :     /* Done */
     324              : 
     325          135 :     table_close(shdescription, NoLock);
     326          135 : }
     327              : 
     328              : /*
     329              :  * DeleteComments -- remove comments for an object
     330              :  *
     331              :  * If subid is nonzero then only comments matching it will be removed.
     332              :  * If subid is zero, all comments matching the oid/classoid will be removed
     333              :  * (this corresponds to deleting a whole object).
     334              :  */
     335              : void
     336       116381 : DeleteComments(Oid oid, Oid classoid, int32 subid)
     337              : {
     338              :     Relation    description;
     339              :     ScanKeyData skey[3];
     340              :     int         nkeys;
     341              :     SysScanDesc sd;
     342              :     HeapTuple   oldtuple;
     343              : 
     344              :     /* Use the index to search for all matching old tuples */
     345              : 
     346       116381 :     ScanKeyInit(&skey[0],
     347              :                 Anum_pg_description_objoid,
     348              :                 BTEqualStrategyNumber, F_OIDEQ,
     349              :                 ObjectIdGetDatum(oid));
     350       116381 :     ScanKeyInit(&skey[1],
     351              :                 Anum_pg_description_classoid,
     352              :                 BTEqualStrategyNumber, F_OIDEQ,
     353              :                 ObjectIdGetDatum(classoid));
     354              : 
     355       116381 :     if (subid != 0)
     356              :     {
     357         1067 :         ScanKeyInit(&skey[2],
     358              :                     Anum_pg_description_objsubid,
     359              :                     BTEqualStrategyNumber, F_INT4EQ,
     360              :                     Int32GetDatum(subid));
     361         1067 :         nkeys = 3;
     362              :     }
     363              :     else
     364       115314 :         nkeys = 2;
     365              : 
     366       116381 :     description = table_open(DescriptionRelationId, RowExclusiveLock);
     367              : 
     368       116381 :     sd = systable_beginscan(description, DescriptionObjIndexId, true,
     369              :                             NULL, nkeys, skey);
     370              : 
     371       116792 :     while ((oldtuple = systable_getnext(sd)) != NULL)
     372          411 :         CatalogTupleDelete(description, &oldtuple->t_self);
     373              : 
     374              :     /* Done */
     375              : 
     376       116381 :     systable_endscan(sd);
     377       116381 :     table_close(description, RowExclusiveLock);
     378       116381 : }
     379              : 
     380              : /*
     381              :  * DeleteSharedComments -- remove comments for a shared object
     382              :  */
     383              : void
     384          790 : DeleteSharedComments(Oid oid, Oid classoid)
     385              : {
     386              :     Relation    shdescription;
     387              :     ScanKeyData skey[2];
     388              :     SysScanDesc sd;
     389              :     HeapTuple   oldtuple;
     390              : 
     391              :     /* Use the index to search for all matching old tuples */
     392              : 
     393          790 :     ScanKeyInit(&skey[0],
     394              :                 Anum_pg_shdescription_objoid,
     395              :                 BTEqualStrategyNumber, F_OIDEQ,
     396              :                 ObjectIdGetDatum(oid));
     397          790 :     ScanKeyInit(&skey[1],
     398              :                 Anum_pg_shdescription_classoid,
     399              :                 BTEqualStrategyNumber, F_OIDEQ,
     400              :                 ObjectIdGetDatum(classoid));
     401              : 
     402          790 :     shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
     403              : 
     404          790 :     sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
     405              :                             NULL, 2, skey);
     406              : 
     407          809 :     while ((oldtuple = systable_getnext(sd)) != NULL)
     408           19 :         CatalogTupleDelete(shdescription, &oldtuple->t_self);
     409              : 
     410              :     /* Done */
     411              : 
     412          790 :     systable_endscan(sd);
     413          790 :     table_close(shdescription, RowExclusiveLock);
     414          790 : }
     415              : 
     416              : /*
     417              :  * GetComment -- get the comment for an object, or null if not found.
     418              :  */
     419              : char *
     420          843 : GetComment(Oid oid, Oid classoid, int32 subid)
     421              : {
     422              :     Relation    description;
     423              :     ScanKeyData skey[3];
     424              :     SysScanDesc sd;
     425              :     TupleDesc   tupdesc;
     426              :     HeapTuple   tuple;
     427              :     char       *comment;
     428              : 
     429              :     /* Use the index to search for a matching old tuple */
     430              : 
     431          843 :     ScanKeyInit(&skey[0],
     432              :                 Anum_pg_description_objoid,
     433              :                 BTEqualStrategyNumber, F_OIDEQ,
     434              :                 ObjectIdGetDatum(oid));
     435          843 :     ScanKeyInit(&skey[1],
     436              :                 Anum_pg_description_classoid,
     437              :                 BTEqualStrategyNumber, F_OIDEQ,
     438              :                 ObjectIdGetDatum(classoid));
     439          843 :     ScanKeyInit(&skey[2],
     440              :                 Anum_pg_description_objsubid,
     441              :                 BTEqualStrategyNumber, F_INT4EQ,
     442              :                 Int32GetDatum(subid));
     443              : 
     444          843 :     description = table_open(DescriptionRelationId, AccessShareLock);
     445          843 :     tupdesc = RelationGetDescr(description);
     446              : 
     447          843 :     sd = systable_beginscan(description, DescriptionObjIndexId, true,
     448              :                             NULL, 3, skey);
     449              : 
     450          843 :     comment = NULL;
     451          843 :     while ((tuple = systable_getnext(sd)) != NULL)
     452              :     {
     453              :         Datum       value;
     454              :         bool        isnull;
     455              : 
     456              :         /* Found the tuple, get description field */
     457          174 :         value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
     458          174 :         if (!isnull)
     459          174 :             comment = TextDatumGetCString(value);
     460          174 :         break;                  /* Assume there can be only one match */
     461              :     }
     462              : 
     463          843 :     systable_endscan(sd);
     464              : 
     465              :     /* Done */
     466          843 :     table_close(description, AccessShareLock);
     467              : 
     468          843 :     return comment;
     469              : }
        

Generated by: LCOV version 2.0-1