LCOV - code coverage report
Current view: top level - src/backend/commands - comment.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 123 130 94.6 %
Date: 2026-02-10 19:17:36 Functions: 6 6 100.0 %
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        7846 : CommentObject(CommentStmt *stmt)
      41             : {
      42             :     Relation    relation;
      43        7846 :     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        7846 :     if (stmt->objtype == OBJECT_DATABASE)
      55             :     {
      56         244 :         char       *database = strVal(stmt->object);
      57             : 
      58         244 :         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        7846 :     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        7846 :     address = get_object_address(stmt->objtype, stmt->object,
      82             :                                  &relation, ShareUpdateExclusiveLock,
      83             :                                  missing_ok);
      84             : 
      85             :     /* Require ownership of the target object. */
      86        7712 :     check_object_ownership(GetUserId(), stmt->objtype, address,
      87             :                            stmt->object, relation);
      88             : 
      89             :     /* Perform other integrity checks as needed. */
      90        7688 :     switch (stmt->objtype)
      91             :     {
      92         210 :         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         210 :             if (relation->rd_rel->relkind != RELKIND_RELATION &&
     104          56 :                 relation->rd_rel->relkind != RELKIND_VIEW &&
     105          56 :                 relation->rd_rel->relkind != RELKIND_MATVIEW &&
     106          56 :                 relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
     107          42 :                 relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
     108          18 :                 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         210 :             break;
     115        7478 :         default:
     116        7478 :             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        7688 :     if (stmt->objtype == OBJECT_DATABASE || stmt->objtype == OBJECT_TABLESPACE
     125        7444 :         || stmt->objtype == OBJECT_ROLE)
     126         252 :         CreateSharedComments(address.objectId, address.classId, stmt->comment);
     127             :     else
     128        7436 :         CreateComments(address.objectId, address.classId, address.objectSubId,
     129        7436 :                        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        7688 :     if (relation != NULL)
     138         632 :         relation_close(relation, NoLock);
     139             : 
     140        7688 :     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       85866 : 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       85866 :     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       85866 :     if (comment != NULL && strlen(comment) == 0)
     167           0 :         comment = NULL;
     168             : 
     169             :     /* Prepare to form or update a tuple, if necessary */
     170       85866 :     if (comment != NULL)
     171             :     {
     172      428840 :         for (i = 0; i < Natts_pg_description; i++)
     173             :         {
     174      343072 :             nulls[i] = false;
     175      343072 :             replaces[i] = true;
     176             :         }
     177       85768 :         values[Anum_pg_description_objoid - 1] = ObjectIdGetDatum(oid);
     178       85768 :         values[Anum_pg_description_classoid - 1] = ObjectIdGetDatum(classoid);
     179       85768 :         values[Anum_pg_description_objsubid - 1] = Int32GetDatum(subid);
     180       85768 :         values[Anum_pg_description_description - 1] = CStringGetTextDatum(comment);
     181             :     }
     182             : 
     183             :     /* Use the index to search for a matching old tuple */
     184             : 
     185       85866 :     ScanKeyInit(&skey[0],
     186             :                 Anum_pg_description_objoid,
     187             :                 BTEqualStrategyNumber, F_OIDEQ,
     188             :                 ObjectIdGetDatum(oid));
     189       85866 :     ScanKeyInit(&skey[1],
     190             :                 Anum_pg_description_classoid,
     191             :                 BTEqualStrategyNumber, F_OIDEQ,
     192             :                 ObjectIdGetDatum(classoid));
     193       85866 :     ScanKeyInit(&skey[2],
     194             :                 Anum_pg_description_objsubid,
     195             :                 BTEqualStrategyNumber, F_INT4EQ,
     196             :                 Int32GetDatum(subid));
     197             : 
     198       85866 :     description = table_open(DescriptionRelationId, RowExclusiveLock);
     199             : 
     200       85866 :     sd = systable_beginscan(description, DescriptionObjIndexId, true,
     201             :                             NULL, 3, skey);
     202             : 
     203       85866 :     while ((oldtuple = systable_getnext(sd)) != NULL)
     204             :     {
     205             :         /* Found the old tuple, so delete or update it */
     206             : 
     207         172 :         if (comment == NULL)
     208          98 :             CatalogTupleDelete(description, &oldtuple->t_self);
     209             :         else
     210             :         {
     211          74 :             newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(description), values,
     212             :                                          nulls, replaces);
     213          74 :             CatalogTupleUpdate(description, &oldtuple->t_self, newtuple);
     214             :         }
     215             : 
     216         172 :         break;                  /* Assume there can be only one match */
     217             :     }
     218             : 
     219       85866 :     systable_endscan(sd);
     220             : 
     221             :     /* If we didn't find an old tuple, insert a new one */
     222             : 
     223       85866 :     if (newtuple == NULL && comment != NULL)
     224             :     {
     225       85694 :         newtuple = heap_form_tuple(RelationGetDescr(description),
     226             :                                    values, nulls);
     227       85694 :         CatalogTupleInsert(description, newtuple);
     228             :     }
     229             : 
     230       85866 :     if (newtuple != NULL)
     231       85768 :         heap_freetuple(newtuple);
     232             : 
     233             :     /* Done */
     234             : 
     235       85866 :     table_close(description, NoLock);
     236       85866 : }
     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         252 : CreateSharedComments(Oid oid, Oid classoid, const char *comment)
     249             : {
     250             :     Relation    shdescription;
     251             :     ScanKeyData skey[2];
     252             :     SysScanDesc sd;
     253             :     HeapTuple   oldtuple;
     254         252 :     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         252 :     if (comment != NULL && strlen(comment) == 0)
     262           0 :         comment = NULL;
     263             : 
     264             :     /* Prepare to form or update a tuple, if necessary */
     265         252 :     if (comment != NULL)
     266             :     {
     267         984 :         for (i = 0; i < Natts_pg_shdescription; i++)
     268             :         {
     269         738 :             nulls[i] = false;
     270         738 :             replaces[i] = true;
     271             :         }
     272         246 :         values[Anum_pg_shdescription_objoid - 1] = ObjectIdGetDatum(oid);
     273         246 :         values[Anum_pg_shdescription_classoid - 1] = ObjectIdGetDatum(classoid);
     274         246 :         values[Anum_pg_shdescription_description - 1] = CStringGetTextDatum(comment);
     275             :     }
     276             : 
     277             :     /* Use the index to search for a matching old tuple */
     278             : 
     279         252 :     ScanKeyInit(&skey[0],
     280             :                 Anum_pg_shdescription_objoid,
     281             :                 BTEqualStrategyNumber, F_OIDEQ,
     282             :                 ObjectIdGetDatum(oid));
     283         252 :     ScanKeyInit(&skey[1],
     284             :                 Anum_pg_shdescription_classoid,
     285             :                 BTEqualStrategyNumber, F_OIDEQ,
     286             :                 ObjectIdGetDatum(classoid));
     287             : 
     288         252 :     shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
     289             : 
     290         252 :     sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
     291             :                             NULL, 2, skey);
     292             : 
     293         252 :     while ((oldtuple = systable_getnext(sd)) != NULL)
     294             :     {
     295             :         /* Found the old tuple, so delete or update it */
     296             : 
     297           6 :         if (comment == NULL)
     298           6 :             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           6 :         break;                  /* Assume there can be only one match */
     307             :     }
     308             : 
     309         252 :     systable_endscan(sd);
     310             : 
     311             :     /* If we didn't find an old tuple, insert a new one */
     312             : 
     313         252 :     if (newtuple == NULL && comment != NULL)
     314             :     {
     315         246 :         newtuple = heap_form_tuple(RelationGetDescr(shdescription),
     316             :                                    values, nulls);
     317         246 :         CatalogTupleInsert(shdescription, newtuple);
     318             :     }
     319             : 
     320         252 :     if (newtuple != NULL)
     321         246 :         heap_freetuple(newtuple);
     322             : 
     323             :     /* Done */
     324             : 
     325         252 :     table_close(shdescription, NoLock);
     326         252 : }
     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      231696 : 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      231696 :     ScanKeyInit(&skey[0],
     347             :                 Anum_pg_description_objoid,
     348             :                 BTEqualStrategyNumber, F_OIDEQ,
     349             :                 ObjectIdGetDatum(oid));
     350      231696 :     ScanKeyInit(&skey[1],
     351             :                 Anum_pg_description_classoid,
     352             :                 BTEqualStrategyNumber, F_OIDEQ,
     353             :                 ObjectIdGetDatum(classoid));
     354             : 
     355      231696 :     if (subid != 0)
     356             :     {
     357        2122 :         ScanKeyInit(&skey[2],
     358             :                     Anum_pg_description_objsubid,
     359             :                     BTEqualStrategyNumber, F_INT4EQ,
     360             :                     Int32GetDatum(subid));
     361        2122 :         nkeys = 3;
     362             :     }
     363             :     else
     364      229574 :         nkeys = 2;
     365             : 
     366      231696 :     description = table_open(DescriptionRelationId, RowExclusiveLock);
     367             : 
     368      231696 :     sd = systable_beginscan(description, DescriptionObjIndexId, true,
     369             :                             NULL, nkeys, skey);
     370             : 
     371      232516 :     while ((oldtuple = systable_getnext(sd)) != NULL)
     372         820 :         CatalogTupleDelete(description, &oldtuple->t_self);
     373             : 
     374             :     /* Done */
     375             : 
     376      231696 :     systable_endscan(sd);
     377      231696 :     table_close(description, RowExclusiveLock);
     378      231696 : }
     379             : 
     380             : /*
     381             :  * DeleteSharedComments -- remove comments for a shared object
     382             :  */
     383             : void
     384        1578 : 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        1578 :     ScanKeyInit(&skey[0],
     394             :                 Anum_pg_shdescription_objoid,
     395             :                 BTEqualStrategyNumber, F_OIDEQ,
     396             :                 ObjectIdGetDatum(oid));
     397        1578 :     ScanKeyInit(&skey[1],
     398             :                 Anum_pg_shdescription_classoid,
     399             :                 BTEqualStrategyNumber, F_OIDEQ,
     400             :                 ObjectIdGetDatum(classoid));
     401             : 
     402        1578 :     shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
     403             : 
     404        1578 :     sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
     405             :                             NULL, 2, skey);
     406             : 
     407        1622 :     while ((oldtuple = systable_getnext(sd)) != NULL)
     408          44 :         CatalogTupleDelete(shdescription, &oldtuple->t_self);
     409             : 
     410             :     /* Done */
     411             : 
     412        1578 :     systable_endscan(sd);
     413        1578 :     table_close(shdescription, RowExclusiveLock);
     414        1578 : }
     415             : 
     416             : /*
     417             :  * GetComment -- get the comment for an object, or null if not found.
     418             :  */
     419             : char *
     420        1638 : 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        1638 :     ScanKeyInit(&skey[0],
     432             :                 Anum_pg_description_objoid,
     433             :                 BTEqualStrategyNumber, F_OIDEQ,
     434             :                 ObjectIdGetDatum(oid));
     435        1638 :     ScanKeyInit(&skey[1],
     436             :                 Anum_pg_description_classoid,
     437             :                 BTEqualStrategyNumber, F_OIDEQ,
     438             :                 ObjectIdGetDatum(classoid));
     439        1638 :     ScanKeyInit(&skey[2],
     440             :                 Anum_pg_description_objsubid,
     441             :                 BTEqualStrategyNumber, F_INT4EQ,
     442             :                 Int32GetDatum(subid));
     443             : 
     444        1638 :     description = table_open(DescriptionRelationId, AccessShareLock);
     445        1638 :     tupdesc = RelationGetDescr(description);
     446             : 
     447        1638 :     sd = systable_beginscan(description, DescriptionObjIndexId, true,
     448             :                             NULL, 3, skey);
     449             : 
     450        1638 :     comment = NULL;
     451        1638 :     while ((tuple = systable_getnext(sd)) != NULL)
     452             :     {
     453             :         Datum       value;
     454             :         bool        isnull;
     455             : 
     456             :         /* Found the tuple, get description field */
     457         348 :         value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
     458         348 :         if (!isnull)
     459         348 :             comment = TextDatumGetCString(value);
     460         348 :         break;                  /* Assume there can be only one match */
     461             :     }
     462             : 
     463        1638 :     systable_endscan(sd);
     464             : 
     465             :     /* Done */
     466        1638 :     table_close(description, AccessShareLock);
     467             : 
     468        1638 :     return comment;
     469             : }

Generated by: LCOV version 1.16