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

Generated by: LCOV version 1.14