LCOV - code coverage report
Current view: top level - src/backend/commands - comment.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13beta1 Lines: 119 129 92.2 %
Date: 2020-06-01 10:07:15 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-2020, 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       18646 : CommentObject(CommentStmt *stmt)
      41             : {
      42             :     Relation    relation;
      43       18646 :     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       18646 :     if (stmt->objtype == OBJECT_DATABASE)
      54             :     {
      55         720 :         char       *database = strVal((Value *) stmt->object);
      56             : 
      57         720 :         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       18646 :     address = get_object_address(stmt->objtype, stmt->object,
      73             :                                  &relation, ShareUpdateExclusiveLock, false);
      74             : 
      75             :     /* Require ownership of the target object. */
      76       18564 :     check_object_ownership(GetUserId(), stmt->objtype, address,
      77             :                            stmt->object, relation);
      78             : 
      79             :     /* Perform other integrity checks as needed. */
      80       18556 :     switch (stmt->objtype)
      81             :     {
      82         100 :         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         100 :             if (relation->rd_rel->relkind != RELKIND_RELATION &&
      94          26 :                 relation->rd_rel->relkind != RELKIND_VIEW &&
      95          26 :                 relation->rd_rel->relkind != RELKIND_MATVIEW &&
      96          26 :                 relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
      97          16 :                 relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
      98           4 :                 relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
      99           0 :                 ereport(ERROR,
     100             :                         (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     101             :                          errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
     102             :                                 RelationGetRelationName(relation))));
     103         100 :             break;
     104       18456 :         default:
     105       18456 :             break;
     106             :     }
     107             : 
     108             :     /*
     109             :      * Databases, tablespaces, and roles are cluster-wide objects, so any
     110             :      * comments on those objects are recorded in the shared pg_shdescription
     111             :      * catalog.  Comments on all other objects are recorded in pg_description.
     112             :      */
     113       18556 :     if (stmt->objtype == OBJECT_DATABASE || stmt->objtype == OBJECT_TABLESPACE
     114       17836 :         || stmt->objtype == OBJECT_ROLE)
     115         722 :         CreateSharedComments(address.objectId, address.classId, stmt->comment);
     116             :     else
     117       17834 :         CreateComments(address.objectId, address.classId, address.objectSubId,
     118       17834 :                        stmt->comment);
     119             : 
     120             :     /*
     121             :      * If get_object_address() opened the relation for us, we close it to keep
     122             :      * the reference count correct - but we retain any locks acquired by
     123             :      * get_object_address() until commit time, to guard against concurrent
     124             :      * activity.
     125             :      */
     126       18556 :     if (relation != NULL)
     127         320 :         relation_close(relation, NoLock);
     128             : 
     129       18556 :     return address;
     130             : }
     131             : 
     132             : /*
     133             :  * CreateComments --
     134             :  *
     135             :  * Create a comment for the specified object descriptor.  Inserts a new
     136             :  * pg_description tuple, or replaces an existing one with the same key.
     137             :  *
     138             :  * If the comment given is null or an empty string, instead delete any
     139             :  * existing comment for the specified key.
     140             :  */
     141             : void
     142       18454 : CreateComments(Oid oid, Oid classoid, int32 subid, const char *comment)
     143             : {
     144             :     Relation    description;
     145             :     ScanKeyData skey[3];
     146             :     SysScanDesc sd;
     147             :     HeapTuple   oldtuple;
     148       18454 :     HeapTuple   newtuple = NULL;
     149             :     Datum       values[Natts_pg_description];
     150             :     bool        nulls[Natts_pg_description];
     151             :     bool        replaces[Natts_pg_description];
     152             :     int         i;
     153             : 
     154             :     /* Reduce empty-string to NULL case */
     155       18454 :     if (comment != NULL && strlen(comment) == 0)
     156           0 :         comment = NULL;
     157             : 
     158             :     /* Prepare to form or update a tuple, if necessary */
     159       18454 :     if (comment != NULL)
     160             :     {
     161       91950 :         for (i = 0; i < Natts_pg_description; i++)
     162             :         {
     163       73560 :             nulls[i] = false;
     164       73560 :             replaces[i] = true;
     165             :         }
     166       18390 :         values[Anum_pg_description_objoid - 1] = ObjectIdGetDatum(oid);
     167       18390 :         values[Anum_pg_description_classoid - 1] = ObjectIdGetDatum(classoid);
     168       18390 :         values[Anum_pg_description_objsubid - 1] = Int32GetDatum(subid);
     169       18390 :         values[Anum_pg_description_description - 1] = CStringGetTextDatum(comment);
     170             :     }
     171             : 
     172             :     /* Use the index to search for a matching old tuple */
     173             : 
     174       18454 :     ScanKeyInit(&skey[0],
     175             :                 Anum_pg_description_objoid,
     176             :                 BTEqualStrategyNumber, F_OIDEQ,
     177             :                 ObjectIdGetDatum(oid));
     178       18454 :     ScanKeyInit(&skey[1],
     179             :                 Anum_pg_description_classoid,
     180             :                 BTEqualStrategyNumber, F_OIDEQ,
     181             :                 ObjectIdGetDatum(classoid));
     182       18454 :     ScanKeyInit(&skey[2],
     183             :                 Anum_pg_description_objsubid,
     184             :                 BTEqualStrategyNumber, F_INT4EQ,
     185             :                 Int32GetDatum(subid));
     186             : 
     187       18454 :     description = table_open(DescriptionRelationId, RowExclusiveLock);
     188             : 
     189       18454 :     sd = systable_beginscan(description, DescriptionObjIndexId, true,
     190             :                             NULL, 3, skey);
     191             : 
     192       18454 :     while ((oldtuple = systable_getnext(sd)) != NULL)
     193             :     {
     194             :         /* Found the old tuple, so delete or update it */
     195             : 
     196          72 :         if (comment == NULL)
     197          64 :             CatalogTupleDelete(description, &oldtuple->t_self);
     198             :         else
     199             :         {
     200           8 :             newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(description), values,
     201             :                                          nulls, replaces);
     202           8 :             CatalogTupleUpdate(description, &oldtuple->t_self, newtuple);
     203             :         }
     204             : 
     205          72 :         break;                  /* Assume there can be only one match */
     206             :     }
     207             : 
     208       18454 :     systable_endscan(sd);
     209             : 
     210             :     /* If we didn't find an old tuple, insert a new one */
     211             : 
     212       18454 :     if (newtuple == NULL && comment != NULL)
     213             :     {
     214       18382 :         newtuple = heap_form_tuple(RelationGetDescr(description),
     215             :                                    values, nulls);
     216       18382 :         CatalogTupleInsert(description, newtuple);
     217             :     }
     218             : 
     219       18454 :     if (newtuple != NULL)
     220       18390 :         heap_freetuple(newtuple);
     221             : 
     222             :     /* Done */
     223             : 
     224       18454 :     table_close(description, NoLock);
     225       18454 : }
     226             : 
     227             : /*
     228             :  * CreateSharedComments --
     229             :  *
     230             :  * Create a comment for the specified shared object descriptor.  Inserts a
     231             :  * new pg_shdescription tuple, or replaces an existing one with the same key.
     232             :  *
     233             :  * If the comment given is null or an empty string, instead delete any
     234             :  * existing comment for the specified key.
     235             :  */
     236             : void
     237         722 : CreateSharedComments(Oid oid, Oid classoid, const char *comment)
     238             : {
     239             :     Relation    shdescription;
     240             :     ScanKeyData skey[2];
     241             :     SysScanDesc sd;
     242             :     HeapTuple   oldtuple;
     243         722 :     HeapTuple   newtuple = NULL;
     244             :     Datum       values[Natts_pg_shdescription];
     245             :     bool        nulls[Natts_pg_shdescription];
     246             :     bool        replaces[Natts_pg_shdescription];
     247             :     int         i;
     248             : 
     249             :     /* Reduce empty-string to NULL case */
     250         722 :     if (comment != NULL && strlen(comment) == 0)
     251           0 :         comment = NULL;
     252             : 
     253             :     /* Prepare to form or update a tuple, if necessary */
     254         722 :     if (comment != NULL)
     255             :     {
     256        2888 :         for (i = 0; i < Natts_pg_shdescription; i++)
     257             :         {
     258        2166 :             nulls[i] = false;
     259        2166 :             replaces[i] = true;
     260             :         }
     261         722 :         values[Anum_pg_shdescription_objoid - 1] = ObjectIdGetDatum(oid);
     262         722 :         values[Anum_pg_shdescription_classoid - 1] = ObjectIdGetDatum(classoid);
     263         722 :         values[Anum_pg_shdescription_description - 1] = CStringGetTextDatum(comment);
     264             :     }
     265             : 
     266             :     /* Use the index to search for a matching old tuple */
     267             : 
     268         722 :     ScanKeyInit(&skey[0],
     269             :                 Anum_pg_shdescription_objoid,
     270             :                 BTEqualStrategyNumber, F_OIDEQ,
     271             :                 ObjectIdGetDatum(oid));
     272         722 :     ScanKeyInit(&skey[1],
     273             :                 Anum_pg_shdescription_classoid,
     274             :                 BTEqualStrategyNumber, F_OIDEQ,
     275             :                 ObjectIdGetDatum(classoid));
     276             : 
     277         722 :     shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
     278             : 
     279         722 :     sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
     280             :                             NULL, 2, skey);
     281             : 
     282         722 :     while ((oldtuple = systable_getnext(sd)) != NULL)
     283             :     {
     284             :         /* Found the old tuple, so delete or update it */
     285             : 
     286           0 :         if (comment == NULL)
     287           0 :             CatalogTupleDelete(shdescription, &oldtuple->t_self);
     288             :         else
     289             :         {
     290           0 :             newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(shdescription),
     291             :                                          values, nulls, replaces);
     292           0 :             CatalogTupleUpdate(shdescription, &oldtuple->t_self, newtuple);
     293             :         }
     294             : 
     295           0 :         break;                  /* Assume there can be only one match */
     296             :     }
     297             : 
     298         722 :     systable_endscan(sd);
     299             : 
     300             :     /* If we didn't find an old tuple, insert a new one */
     301             : 
     302         722 :     if (newtuple == NULL && comment != NULL)
     303             :     {
     304         722 :         newtuple = heap_form_tuple(RelationGetDescr(shdescription),
     305             :                                    values, nulls);
     306         722 :         CatalogTupleInsert(shdescription, newtuple);
     307             :     }
     308             : 
     309         722 :     if (newtuple != NULL)
     310         722 :         heap_freetuple(newtuple);
     311             : 
     312             :     /* Done */
     313             : 
     314         722 :     table_close(shdescription, NoLock);
     315         722 : }
     316             : 
     317             : /*
     318             :  * DeleteComments -- remove comments for an object
     319             :  *
     320             :  * If subid is nonzero then only comments matching it will be removed.
     321             :  * If subid is zero, all comments matching the oid/classoid will be removed
     322             :  * (this corresponds to deleting a whole object).
     323             :  */
     324             : void
     325      101622 : DeleteComments(Oid oid, Oid classoid, int32 subid)
     326             : {
     327             :     Relation    description;
     328             :     ScanKeyData skey[3];
     329             :     int         nkeys;
     330             :     SysScanDesc sd;
     331             :     HeapTuple   oldtuple;
     332             : 
     333             :     /* Use the index to search for all matching old tuples */
     334             : 
     335      101622 :     ScanKeyInit(&skey[0],
     336             :                 Anum_pg_description_objoid,
     337             :                 BTEqualStrategyNumber, F_OIDEQ,
     338             :                 ObjectIdGetDatum(oid));
     339      101622 :     ScanKeyInit(&skey[1],
     340             :                 Anum_pg_description_classoid,
     341             :                 BTEqualStrategyNumber, F_OIDEQ,
     342             :                 ObjectIdGetDatum(classoid));
     343             : 
     344      101622 :     if (subid != 0)
     345             :     {
     346        1286 :         ScanKeyInit(&skey[2],
     347             :                     Anum_pg_description_objsubid,
     348             :                     BTEqualStrategyNumber, F_INT4EQ,
     349             :                     Int32GetDatum(subid));
     350        1286 :         nkeys = 3;
     351             :     }
     352             :     else
     353      100336 :         nkeys = 2;
     354             : 
     355      101622 :     description = table_open(DescriptionRelationId, RowExclusiveLock);
     356             : 
     357      101622 :     sd = systable_beginscan(description, DescriptionObjIndexId, true,
     358             :                             NULL, nkeys, skey);
     359             : 
     360      102040 :     while ((oldtuple = systable_getnext(sd)) != NULL)
     361         418 :         CatalogTupleDelete(description, &oldtuple->t_self);
     362             : 
     363             :     /* Done */
     364             : 
     365      101622 :     systable_endscan(sd);
     366      101622 :     table_close(description, RowExclusiveLock);
     367      101622 : }
     368             : 
     369             : /*
     370             :  * DeleteSharedComments -- remove comments for a shared object
     371             :  */
     372             : void
     373         626 : DeleteSharedComments(Oid oid, Oid classoid)
     374             : {
     375             :     Relation    shdescription;
     376             :     ScanKeyData skey[2];
     377             :     SysScanDesc sd;
     378             :     HeapTuple   oldtuple;
     379             : 
     380             :     /* Use the index to search for all matching old tuples */
     381             : 
     382         626 :     ScanKeyInit(&skey[0],
     383             :                 Anum_pg_shdescription_objoid,
     384             :                 BTEqualStrategyNumber, F_OIDEQ,
     385             :                 ObjectIdGetDatum(oid));
     386         626 :     ScanKeyInit(&skey[1],
     387             :                 Anum_pg_shdescription_classoid,
     388             :                 BTEqualStrategyNumber, F_OIDEQ,
     389             :                 ObjectIdGetDatum(classoid));
     390             : 
     391         626 :     shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
     392             : 
     393         626 :     sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
     394             :                             NULL, 2, skey);
     395             : 
     396         632 :     while ((oldtuple = systable_getnext(sd)) != NULL)
     397           6 :         CatalogTupleDelete(shdescription, &oldtuple->t_self);
     398             : 
     399             :     /* Done */
     400             : 
     401         626 :     systable_endscan(sd);
     402         626 :     table_close(shdescription, RowExclusiveLock);
     403         626 : }
     404             : 
     405             : /*
     406             :  * GetComment -- get the comment for an object, or null if not found.
     407             :  */
     408             : char *
     409         536 : GetComment(Oid oid, Oid classoid, int32 subid)
     410             : {
     411             :     Relation    description;
     412             :     ScanKeyData skey[3];
     413             :     SysScanDesc sd;
     414             :     TupleDesc   tupdesc;
     415             :     HeapTuple   tuple;
     416             :     char       *comment;
     417             : 
     418             :     /* Use the index to search for a matching old tuple */
     419             : 
     420         536 :     ScanKeyInit(&skey[0],
     421             :                 Anum_pg_description_objoid,
     422             :                 BTEqualStrategyNumber, F_OIDEQ,
     423             :                 ObjectIdGetDatum(oid));
     424         536 :     ScanKeyInit(&skey[1],
     425             :                 Anum_pg_description_classoid,
     426             :                 BTEqualStrategyNumber, F_OIDEQ,
     427             :                 ObjectIdGetDatum(classoid));
     428         536 :     ScanKeyInit(&skey[2],
     429             :                 Anum_pg_description_objsubid,
     430             :                 BTEqualStrategyNumber, F_INT4EQ,
     431             :                 Int32GetDatum(subid));
     432             : 
     433         536 :     description = table_open(DescriptionRelationId, AccessShareLock);
     434         536 :     tupdesc = RelationGetDescr(description);
     435             : 
     436         536 :     sd = systable_beginscan(description, DescriptionObjIndexId, true,
     437             :                             NULL, 3, skey);
     438             : 
     439         536 :     comment = NULL;
     440         536 :     while ((tuple = systable_getnext(sd)) != NULL)
     441             :     {
     442             :         Datum       value;
     443             :         bool        isnull;
     444             : 
     445             :         /* Found the tuple, get description field */
     446         132 :         value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
     447         132 :         if (!isnull)
     448         132 :             comment = TextDatumGetCString(value);
     449         132 :         break;                  /* Assume there can be only one match */
     450             :     }
     451             : 
     452         536 :     systable_endscan(sd);
     453             : 
     454             :     /* Done */
     455         536 :     table_close(description, AccessShareLock);
     456             : 
     457         536 :     return comment;
     458             : }

Generated by: LCOV version 1.13