mirror of
https://github.com/zebrajr/postgres.git
synced 2025-12-07 12:20:31 +01:00
The following renaming is done so as source files related to index access methods are more consistent with table access methods (the original names used for index AMs ware too generic, and could be confused as including features related to table AMs): - amapi.h -> indexam.h. - amapi.c -> indexamapi.c. Here we have an equivalent with backend/access/table/tableamapi.c. - amvalidate.c -> indexamvalidate.c. - amvalidate.h -> indexamvalidate.h. - genam.c -> indexgenam.c. - genam.h -> indexgenam.h. This has been discussed during the development of v12 when table AM was worked on, but the renaming never happened. Author: Michael Paquier Reviewed-by: Fabien Coelho, Julien Rouhaud Discussion: https://postgr.es/m/20191223053434.GF34339@paquier.xyz
459 lines
12 KiB
C
459 lines
12 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* comment.c
|
|
*
|
|
* PostgreSQL object comments utility code.
|
|
*
|
|
* Copyright (c) 1996-2019, PostgreSQL Global Development Group
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/commands/comment.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/htup_details.h"
|
|
#include "access/indexgenam.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((Value *) 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("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
|
|
RelationGetRelationName(relation))));
|
|
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;
|
|
}
|