/*-------------------------------------------------------------------------
*
* comment . c
*
* PostgreSQL object comments utility code .
*
* Copyright ( c ) 1996 - 2025 , PostgreSQL Global Development Group
*
* IDENTIFICATION
* src / backend / commands / comment . c
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/htup_details.h"
#include "access/relation.h"
#include "access/table.h"
#include "catalog/indexing.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_description.h"
#include "catalog/pg_shdescription.h"
#include "commands/comment.h"
#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/rel.h"
/*
* CommentObject - -
*
* This routine is used to add the associated comment into
* pg_description for the object specified by the given SQL command .
*/
ObjectAddress
CommentObject(CommentStmt *stmt)
{
Relation relation;
ObjectAddress address = InvalidObjectAddress;
/*
* When loading a dump , we may see a COMMENT ON DATABASE for the old name
* of the database . Erroring out would prevent pg_restore from completing
* ( which is really pg_restore ' s fault , but for now we will work around
* the problem here ) . Consensus is that the best fix is to treat wrong
* database name as a WARNING not an ERROR ; hence , the following special
* case .
*/
if (stmt->objtype == OBJECT_DATABASE)
{
char *database = strVal(stmt->object);
if (!OidIsValid(get_database_oid(database, true)))
{
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_DATABASE),
errmsg("database \" %s\" does not exist" , database)));
return address;
}
}
/*
* Translate the parser representation that identifies this object into an
* ObjectAddress . get_object_address ( ) will throw an error if the object
* does not exist , and will also acquire a lock on the target to guard
* against concurrent DROP operations .
*/
address = get_object_address(stmt->objtype, stmt->object,
&relation, ShareUpdateExclusiveLock, false);
/* Require ownership of the target object. */
check_object_ownership(GetUserId(), stmt->objtype, address,
stmt->object, relation);
/* Perform other integrity checks as needed. */
switch (stmt->objtype)
{
case OBJECT_COLUMN:
/*
* Allow comments only on columns of tables , views , materialized
* views , composite types , and foreign tables ( which are the only
* relkinds for which pg_dump will dump per - column comments ) . In
* particular we wish to disallow comments on index columns ,
* because the naming of an index ' s columns may change across PG
* versions , so dumping per - column comments could create reload
* failures .
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot set comment on relation \" %s\"" ,
RelationGetRelationName(relation)),
errdetail_relkind_not_supported(relation->rd_rel->relkind)));
break;
default:
break;
}
/*
* Databases , tablespaces , and roles are cluster - wide objects , so any
* comments on those objects are recorded in the shared pg_shdescription
* catalog . Comments on all other objects are recorded in pg_description .
*/
if (stmt->objtype == OBJECT_DATABASE || stmt->objtype == OBJECT_TABLESPACE
|| stmt->objtype == OBJECT_ROLE)
CreateSharedComments(address.objectId, address.classId, stmt->comment);
else
CreateComments(address.objectId, address.classId, address.objectSubId,
stmt->comment);
/*
* If get_object_address ( ) opened the relation for us , we close it to keep
* the reference count correct - but we retain any locks acquired by
* get_object_address ( ) until commit time , to guard against concurrent
* activity .
*/
if (relation != NULL)
relation_close(relation, NoLock);
return address;
}
/*
* CreateComments - -
*
* Create a comment for the specified object descriptor . Inserts a new
* pg_description tuple , or replaces an existing one with the same key .
*
* If the comment given is null or an empty string , instead delete any
* existing comment for the specified key .
*/
void
CreateComments(Oid oid, Oid classoid, int32 subid, const char *comment)
{
Relation description;
ScanKeyData skey[3 ];
SysScanDesc sd;
HeapTuple oldtuple;
HeapTuple newtuple = NULL;
Datum values[Natts_pg_description];
bool nulls[Natts_pg_description];
bool replaces[Natts_pg_description];
int i;
/* Reduce empty-string to NULL case */
if (comment != NULL && strlen(comment) == 0 )
comment = NULL;
/* Prepare to form or update a tuple, if necessary */
if (comment != NULL)
{
for (i = 0 ; i < Natts_pg_description; i++)
{
nulls[i] = false;
replaces[i] = true;
}
values[Anum_pg_description_objoid - 1 ] = ObjectIdGetDatum(oid);
values[Anum_pg_description_classoid - 1 ] = ObjectIdGetDatum(classoid);
values[Anum_pg_description_objsubid - 1 ] = Int32GetDatum(subid);
values[Anum_pg_description_description - 1 ] = CStringGetTextDatum(comment);
}
/* Use the index to search for a matching old tuple */
ScanKeyInit(&skey[0 ],
Anum_pg_description_objoid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(oid));
ScanKeyInit(&skey[1 ],
Anum_pg_description_classoid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(classoid));
ScanKeyInit(&skey[2 ],
Anum_pg_description_objsubid,
BTEqualStrategyNumber, F_INT4EQ,
Int32GetDatum(subid));
description = table_open(DescriptionRelationId, RowExclusiveLock);
sd = systable_beginscan(description, DescriptionObjIndexId, true,
NULL, 3 , skey);
while ((oldtuple = systable_getnext(sd)) != NULL)
{
/* Found the old tuple, so delete or update it */
if (comment == NULL)
CatalogTupleDelete(description, &oldtuple->t_self);
else
{
newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(description), values,
nulls, replaces);
CatalogTupleUpdate(description, &oldtuple->t_self, newtuple);
}
break; /* Assume there can be only one match */
}
systable_endscan(sd);
/* If we didn't find an old tuple, insert a new one */
if (newtuple == NULL && comment != NULL)
{
newtuple = heap_form_tuple(RelationGetDescr(description),
values, nulls);
CatalogTupleInsert(description, newtuple);
}
if (newtuple != NULL)
heap_freetuple(newtuple);
/* Done */
table_close(description, NoLock);
}
/*
* CreateSharedComments - -
*
* Create a comment for the specified shared object descriptor . Inserts a
* new pg_shdescription tuple , or replaces an existing one with the same key .
*
* If the comment given is null or an empty string , instead delete any
* existing comment for the specified key .
*/
void
CreateSharedComments(Oid oid, Oid classoid, const char *comment)
{
Relation shdescription;
ScanKeyData skey[2 ];
SysScanDesc sd;
HeapTuple oldtuple;
HeapTuple newtuple = NULL;
Datum values[Natts_pg_shdescription];
bool nulls[Natts_pg_shdescription];
bool replaces[Natts_pg_shdescription];
int i;
/* Reduce empty-string to NULL case */
if (comment != NULL && strlen(comment) == 0 )
comment = NULL;
/* Prepare to form or update a tuple, if necessary */
if (comment != NULL)
{
for (i = 0 ; i < Natts_pg_shdescription; i++)
{
nulls[i] = false;
replaces[i] = true;
}
values[Anum_pg_shdescription_objoid - 1 ] = ObjectIdGetDatum(oid);
values[Anum_pg_shdescription_classoid - 1 ] = ObjectIdGetDatum(classoid);
values[Anum_pg_shdescription_description - 1 ] = CStringGetTextDatum(comment);
}
/* Use the index to search for a matching old tuple */
ScanKeyInit(&skey[0 ],
Anum_pg_shdescription_objoid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(oid));
ScanKeyInit(&skey[1 ],
Anum_pg_shdescription_classoid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(classoid));
shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
NULL, 2 , skey);
while ((oldtuple = systable_getnext(sd)) != NULL)
{
/* Found the old tuple, so delete or update it */
if (comment == NULL)
CatalogTupleDelete(shdescription, &oldtuple->t_self);
else
{
newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(shdescription),
values, nulls, replaces);
CatalogTupleUpdate(shdescription, &oldtuple->t_self, newtuple);
}
break; /* Assume there can be only one match */
}
systable_endscan(sd);
/* If we didn't find an old tuple, insert a new one */
if (newtuple == NULL && comment != NULL)
{
newtuple = heap_form_tuple(RelationGetDescr(shdescription),
values, nulls);
CatalogTupleInsert(shdescription, newtuple);
}
if (newtuple != NULL)
heap_freetuple(newtuple);
/* Done */
table_close(shdescription, NoLock);
}
/*
* DeleteComments - - remove comments for an object
*
* If subid is nonzero then only comments matching it will be removed .
* If subid is zero , all comments matching the oid / classoid will be removed
* ( this corresponds to deleting a whole object ) .
*/
void
DeleteComments(Oid oid, Oid classoid, int32 subid)
{
Relation description;
ScanKeyData skey[3 ];
int nkeys;
SysScanDesc sd;
HeapTuple oldtuple;
/* Use the index to search for all matching old tuples */
ScanKeyInit(&skey[0 ],
Anum_pg_description_objoid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(oid));
ScanKeyInit(&skey[1 ],
Anum_pg_description_classoid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(classoid));
if (subid != 0 )
{
ScanKeyInit(&skey[2 ],
Anum_pg_description_objsubid,
BTEqualStrategyNumber, F_INT4EQ,
Int32GetDatum(subid));
nkeys = 3 ;
}
else
nkeys = 2 ;
description = table_open(DescriptionRelationId, RowExclusiveLock);
sd = systable_beginscan(description, DescriptionObjIndexId, true,
NULL, nkeys, skey);
while ((oldtuple = systable_getnext(sd)) != NULL)
CatalogTupleDelete(description, &oldtuple->t_self);
/* Done */
systable_endscan(sd);
table_close(description, RowExclusiveLock);
}
/*
* DeleteSharedComments - - remove comments for a shared object
*/
void
DeleteSharedComments(Oid oid, Oid classoid)
{
Relation shdescription;
ScanKeyData skey[2 ];
SysScanDesc sd;
HeapTuple oldtuple;
/* Use the index to search for all matching old tuples */
ScanKeyInit(&skey[0 ],
Anum_pg_shdescription_objoid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(oid));
ScanKeyInit(&skey[1 ],
Anum_pg_shdescription_classoid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(classoid));
shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);
sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
NULL, 2 , skey);
while ((oldtuple = systable_getnext(sd)) != NULL)
CatalogTupleDelete(shdescription, &oldtuple->t_self);
/* Done */
systable_endscan(sd);
table_close(shdescription, RowExclusiveLock);
}
/*
* GetComment - - get the comment for an object , or null if not found .
*/
char *
GetComment(Oid oid, Oid classoid, int32 subid)
{
Relation description;
ScanKeyData skey[3 ];
SysScanDesc sd;
TupleDesc tupdesc;
HeapTuple tuple;
char *comment;
/* Use the index to search for a matching old tuple */
ScanKeyInit(&skey[0 ],
Anum_pg_description_objoid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(oid));
ScanKeyInit(&skey[1 ],
Anum_pg_description_classoid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(classoid));
ScanKeyInit(&skey[2 ],
Anum_pg_description_objsubid,
BTEqualStrategyNumber, F_INT4EQ,
Int32GetDatum(subid));
description = table_open(DescriptionRelationId, AccessShareLock);
tupdesc = RelationGetDescr(description);
sd = systable_beginscan(description, DescriptionObjIndexId, true,
NULL, 3 , skey);
comment = NULL;
while ((tuple = systable_getnext(sd)) != NULL)
{
Datum value;
bool isnull;
/* Found the tuple, get description field */
value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
if (!isnull)
comment = TextDatumGetCString(value);
break; /* Assume there can be only one match */
}
systable_endscan(sd);
/* Done */
table_close(description, AccessShareLock);
return comment;
}
Messung V0.5 in Prozent C=93 H=69 G=81
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet am 2026-08-10)
¤
*© Formatika GbR, Deutschland