diff options
Diffstat (limited to 'src/backend/commands/comment.c')
-rw-r--r-- | src/backend/commands/comment.c | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c new file mode 100644 index 0000000..26c4d59 --- /dev/null +++ b/src/backend/commands/comment.c @@ -0,0 +1,459 @@ +/*------------------------------------------------------------------------- + * + * comment.c + * + * PostgreSQL object comments utility code. + * + * Copyright (c) 1996-2023, 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; +} |