1
0
mirror of https://github.com/postgres/postgres.git synced 2025-04-22 23:02:54 +03:00
Bruce Momjian 577e21b34f Hello.
The following patch extends the COMMENT ON functionality to the
rest of the database objects beyond just tables, columns, and views. The
grammer of the COMMENT ON statement now looks like:

COMMENT ON [
  [ DATABASE | INDEX | RULE | SEQUENCE | TABLE | TYPE | VIEW ] <objname>
|

  COLUMN <relation>.<attribute> |
  AGGREGATE <aggname> <aggtype> |
  FUNCTION <funcname> (arg1, arg2, ...) |
  OPERATOR <op> (leftoperand_typ rightoperand_typ) |
  TRIGGER <triggername> ON relname>

Mike Mascari
(mascarim@yahoo.com)
1999-10-26 03:12:39 +00:00

797 lines
23 KiB
C

/*-------------------------------------------------------------------------
*
* comment.c
*
* PostgreSQL object comments utility code.
*
* Copyright (c) 1999, PostgreSQL Global Development Group
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
#include "catalog/catname.h"
#include "catalog/indexing.h"
#include "catalog/heap.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_database.h"
#include "catalog/pg_description.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shadow.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "commands/comment.h"
#include "miscadmin.h"
#include "rewrite/rewriteRemove.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/syscache.h"
#include "../backend/parser/parse.h"
/*------------------------------------------------------------------
* Static Function Prototypes --
*
* The following protoypes are declared static so as not to conflict
* with any other routines outside this module. These routines are
* called by the public function CommentObject() routine to create
* the appropriate comment for the specific object type.
*------------------------------------------------------------------
*/
static void CommentRelation(int objtype, char *relation, char *comment);
static void CommentAttribute(char *relation, char *attrib, char *comment);
static void CommentDatabase(char *database, char *comment);
static void CommentRewrite(char *rule, char *comment);
static void CommentType(char *type, char *comment);
static void CommentAggregate(char *aggregate, char *aggtype, char *comment);
static void CommentProc(char *function, List *arguments, char *comment);
static void CommentOperator(char *opname, List *arguments, char *comment);
static void CommentTrigger(char *trigger, char *relation, char *comments);
/*------------------------------------------------------------------
* CommentObject --
*
* This routine is used to add the associated comment into
* pg_description for the object specified by the paramters handed
* to this routine. If the routine cannot determine an Oid to
* associated with the parameters handed to this routine, an
* error is thrown. Otherwise the comment is added to pg_description
* by calling the CreateComments() routine. If the comments were
* empty, CreateComments() will drop any comments associated with
* the object.
*------------------------------------------------------------------
*/
void CommentObject(int objtype, char *objname, char *objproperty,
List *objlist, char *comment) {
switch (objtype) {
case (INDEX):
case (SEQUENCE):
case (TABLE):
case (VIEW):
CommentRelation(objtype, objname, comment);
break;
case (COLUMN):
CommentAttribute(objname, objproperty, comment);
break;
case (DATABASE):
CommentDatabase(objname, comment);
break;
case (RULE):
CommentRewrite(objname, comment);
break;
case (TYPE_P):
CommentType(objname, comment);
break;
case (AGGREGATE):
CommentAggregate(objname, objproperty, comment);
break;
case (FUNCTION):
CommentProc(objname, objlist, comment);
break;
case (OPERATOR):
CommentOperator(objname, objlist, comment);
break;
case (TRIGGER):
CommentTrigger(objname, objproperty, comment);
break;
default:
elog(ERROR, "An attempt was made to comment on a unkown type: %i",
objtype);
}
}
/*------------------------------------------------------------------
* CreateComments --
*
* This routine is handed the oid and the command associated
* with that id and will insert, update, or delete (if the
* comment is an empty string or a NULL pointer) the associated
* comment from the system cataloge, pg_description.
*
*------------------------------------------------------------------
*/
void CreateComments(Oid oid, char *comment) {
Relation description;
TupleDesc tupDesc;
HeapScanDesc scan;
ScanKeyData entry;
HeapTuple desctuple = NULL, searchtuple;
Datum values[Natts_pg_description];
char nulls[Natts_pg_description];
char replaces[Natts_pg_description];
bool modified = false;
int i;
/*** Open pg_description, form a new tuple, if necessary ***/
description = heap_openr(DescriptionRelationName, RowExclusiveLock);
tupDesc = description->rd_att;
if ((comment != NULL) && (strlen(comment) > 0)) {
for (i = 0; i < Natts_pg_description; i++) {
nulls[i] = ' ';
replaces[i] = 'r';
values[i] = (Datum) NULL;
}
i = 0;
values[i++] = ObjectIdGetDatum(oid);
values[i++] = (Datum) fmgr(F_TEXTIN, comment);
}
/*** Now, open pg_description and attempt to find the old tuple ***/
ScanKeyEntryInitialize(&entry, 0x0, Anum_pg_description_objoid, F_OIDEQ,
ObjectIdGetDatum(oid));
scan = heap_beginscan(description, false, SnapshotNow, 1, &entry);
searchtuple = heap_getnext(scan, 0);
/*** If a previous tuple exists, either delete or prep replacement ***/
if (HeapTupleIsValid(searchtuple)) {
/*** If the comment is blank, call heap_delete, else heap_replace ***/
if ((comment == NULL) || (strlen(comment) == 0)) {
heap_delete(description, &searchtuple->t_self, NULL);
} else {
desctuple = heap_modifytuple(searchtuple, description, values,
nulls, replaces);
setheapoverride(true);
heap_replace(description, &searchtuple->t_self, desctuple, NULL);
setheapoverride(false);
modified = TRUE;
}
} else {
desctuple = heap_formtuple(tupDesc, values, nulls);
heap_insert(description, desctuple);
modified = TRUE;
}
/*** Complete the scan, update indices, if necessary ***/
heap_endscan(scan);
if (modified) {
if (RelationGetForm(description)->relhasindex) {
Relation idescs[Num_pg_description_indices];
CatalogOpenIndices(Num_pg_description_indices,
Name_pg_description_indices, idescs);
CatalogIndexInsert(idescs, Num_pg_description_indices, description,
desctuple);
CatalogCloseIndices(Num_pg_description_indices, idescs);
}
pfree(desctuple);
}
heap_close(description, RowExclusiveLock);
}
/*------------------------------------------------------------------
* DeleteComments --
*
* This routine is used to purge any comments
* associated with the Oid handed to this routine,
* regardless of the actual object type. It is
* called, for example, when a relation is destroyed.
*------------------------------------------------------------------
*/
void DeleteComments(Oid oid) {
Relation description;
TupleDesc tupDesc;
ScanKeyData entry;
HeapScanDesc scan;
HeapTuple searchtuple;
description = heap_openr(DescriptionRelationName, RowExclusiveLock);
tupDesc = description->rd_att;
/*** Now, open pg_description and attempt to find the old tuple ***/
ScanKeyEntryInitialize(&entry, 0x0, Anum_pg_description_objoid, F_OIDEQ,
ObjectIdGetDatum(oid));
scan = heap_beginscan(description, false, SnapshotNow, 1, &entry);
searchtuple = heap_getnext(scan, 0);
/*** If a previous tuple exists, delete it ***/
if (HeapTupleIsValid(searchtuple)) {
heap_delete(description, &searchtuple->t_self, NULL);
}
/*** Complete the scan, update indices, if necessary ***/
heap_endscan(scan);
heap_close(description, RowExclusiveLock);
}
/*------------------------------------------------------------------
* CommentRelation --
*
* This routine is used to add/drop a comment from a relation, where
* a relation is a TABLE, SEQUENCE, VIEW or INDEX. The routine simply
* finds the relation name by searching the system cache, locating
* the appropriate tuple, and inserting a comment using that
* tuple's oid. Its parameters are the relation name and comments.
*------------------------------------------------------------------
*/
void CommentRelation(int reltype, char *relname, char *comment) {
HeapTuple reltuple;
Oid oid;
char relkind;
/*** First, check object security ***/
#ifndef NO_SECURITY
if (!pg_ownercheck(GetPgUserName(), relname, RELNAME)) {
elog(ERROR, "you are not permitted to comment on class '%s'", relname);
}
#endif
/*** Now, attempt to find the oid in the cached version of pg_class ***/
reltuple = SearchSysCacheTuple(RELNAME, PointerGetDatum(relname),
0, 0, 0);
if (!HeapTupleIsValid(reltuple)) {
elog(ERROR, "relation '%s' does not exist", relname);
}
oid = reltuple->t_data->t_oid;
/*** Next, verify that the relation type matches the intent ***/
relkind = ((Form_pg_class) GETSTRUCT(reltuple))->relkind;
switch (reltype) {
case (INDEX):
if (relkind != 'i') {
elog(ERROR, "relation '%s' is not an index", relname);
}
break;
case (TABLE):
if (relkind != 'r') {
elog(ERROR, "relation '%s' is not a table", relname);
}
break;
case (VIEW):
if (relkind != 'r') {
elog(ERROR, "relation '%s' is not a view", relname);
}
break;
case (SEQUENCE):
if (relkind != 'S') {
elog(ERROR, "relation '%s' is not a sequence", relname);
}
break;
}
/*** Create the comments using the tuple's oid ***/
CreateComments(oid, comment);
}
/*------------------------------------------------------------------
* CommentAttribute --
*
* This routine is used to add/drop a comment from an attribute
* such as a table's column. The routine will check security
* restrictions and then attempt to fetch the oid of the associated
* attribute. If successful, a comment is added/dropped, else an
* elog() exception is thrown. The parameters are the relation
* and attribute names, and the comments
*------------------------------------------------------------------
*/
void CommentAttribute(char *relname, char *attrname, char *comment) {
Relation relation;
HeapTuple attrtuple;
Oid oid;
/*** First, check object security ***/
#ifndef NO_SECURITY
if (!pg_ownercheck(GetPgUserName(), relname, RELNAME)) {
elog(ERROR, "you are not permitted to comment on class '%s\'", relname);
}
#endif
/*** Now, fetch the attribute oid from the system cache ***/
relation = heap_openr(relname, AccessShareLock);
attrtuple = SearchSysCacheTuple(ATTNAME, ObjectIdGetDatum(relation->rd_id),
PointerGetDatum(attrname), 0, 0);
if (!HeapTupleIsValid(attrtuple)) {
elog(ERROR, "'%s' is not an attribute of class '%s'",
attrname, relname);
}
oid = attrtuple->t_data->t_oid;
/*** Call CreateComments() to create/drop the comments ***/
CreateComments(oid, comment);
/*** Now, close the heap relation and return ***/
heap_close(relation, AccessShareLock);
}
/*------------------------------------------------------------------
* CommentDatabase --
*
* This routine is used to add/drop any user-comments a user might
* have regarding the specified database. The routine will check
* security for owner permissions, and, if succesful, will then
* attempt to find the oid of the database specified. Once found,
* a comment is added/dropped using the CreateComments() routine.
*------------------------------------------------------------------
*/
void CommentDatabase(char *database, char *comment) {
Relation pg_database;
HeapTuple dbtuple, usertuple;
ScanKeyData entry;
HeapScanDesc scan;
Oid oid;
bool superuser;
int4 dba, userid;
char *username;
/*** First find the tuple in pg_database for the database ***/
pg_database = heap_openr(DatabaseRelationName, AccessShareLock);
ScanKeyEntryInitialize(&entry, 0, Anum_pg_database_datname,
F_NAMEEQ, NameGetDatum(database));
scan = heap_beginscan(pg_database, 0, SnapshotNow, 1, &entry);
dbtuple = heap_getnext(scan, 0);
/*** Validate database exists, and fetch the dba id and oid ***/
if (!HeapTupleIsValid(dbtuple)) {
elog(ERROR, "database '%s' does not exist", database);
}
dba = ((Form_pg_database) GETSTRUCT(dbtuple))->datdba;
oid = dbtuple->t_data->t_oid;
/*** Now, fetch user information ***/
username = GetPgUserName();
usertuple = SearchSysCacheTuple(USENAME, PointerGetDatum(username),
0, 0, 0);
if (!HeapTupleIsValid(usertuple)) {
elog(ERROR, "current user '%s' does not exist", username);
}
userid = ((Form_pg_shadow) GETSTRUCT(usertuple))->usesysid;
superuser = ((Form_pg_shadow) GETSTRUCT(usertuple))->usesuper;
/*** Allow if the userid matches the database dba or is a superuser ***/
#ifndef NO_SECURITY
if (!(superuser || (userid == dba))) {
elog(ERROR, "you are not permitted to comment on database '%s'",
database);
}
#endif
/*** Create the comments with the pg_database oid ***/
CreateComments(oid, comment);
/*** Complete the scan and close any opened relations ***/
heap_endscan(scan);
heap_close(pg_database, AccessShareLock);
}
/*------------------------------------------------------------------
* CommentRewrite --
*
* This routine is used to add/drop any user-comments a user might
* have regarding a specified RULE. The rule is specified by name
* and, if found, and the user has appropriate permissions, a
* comment will be added/dropped using the CreateComments() routine.
*------------------------------------------------------------------
*/
void CommentRewrite(char *rule, char *comment) {
HeapTuple rewritetuple;
Oid oid;
char *user, *relation;
int aclcheck;
/*** First, validate user ***/
#ifndef NO_SECURITY
user = GetPgUserName();
relation = RewriteGetRuleEventRel(rule);
aclcheck = pg_aclcheck(relation, user, ACL_RU);
if (aclcheck != ACLCHECK_OK) {
elog(ERROR, "you are not permitted to comment on rule '%s'",
rule);
}
#endif
/*** Next, find the rule's oid ***/
rewritetuple = SearchSysCacheTuple(REWRITENAME, PointerGetDatum(rule),
0, 0, 0);
if (!HeapTupleIsValid(rewritetuple)) {
elog(ERROR, "rule '%s' does not exist", rule);
}
oid = rewritetuple->t_data->t_oid;
/*** Call CreateComments() to create/drop the comments ***/
CreateComments(oid, comment);
}
/*------------------------------------------------------------------
* CommentType --
*
* This routine is used to add/drop any user-comments a user might
* have regarding a TYPE. The type is specified by name
* and, if found, and the user has appropriate permissions, a
* comment will be added/dropped using the CreateComments() routine.
* The type's name and the comments are the paramters to this routine.
*------------------------------------------------------------------
*/
void CommentType(char *type, char *comment) {
HeapTuple typetuple;
Oid oid;
char *user;
/*** First, validate user ***/
#ifndef NO_SECURITY
user = GetPgUserName();
if (!pg_ownercheck(user, type, TYPNAME)) {
elog(ERROR, "you are not permitted to comment on type '%s'",
type);
}
#endif
/*** Next, find the type's oid ***/
typetuple = SearchSysCacheTuple(TYPNAME, PointerGetDatum(type),
0, 0, 0);
if (!HeapTupleIsValid(typetuple)) {
elog(ERROR, "type '%s' does not exist", type);
}
oid = typetuple->t_data->t_oid;
/*** Call CreateComments() to create/drop the comments ***/
CreateComments(oid, comment);
}
/*------------------------------------------------------------------
* CommentAggregate --
*
* This routine is used to allow a user to provide comments on an
* aggregate function. The aggregate function is determined by both
* its name and its argument type, which, with the comments are
* the three parameters handed to this routine.
*------------------------------------------------------------------
*/
void CommentAggregate(char *aggregate, char *argument, char *comment) {
HeapTuple aggtuple;
Oid baseoid, oid;
bool defined;
char *user;
/*** First, attempt to determine the base aggregate oid ***/
if (argument) {
baseoid = TypeGet(argument, &defined);
if (!OidIsValid(baseoid)) {
elog(ERROR, "aggregate type '%s' does not exist", argument);
}
} else {
baseoid = 0;
}
/*** Next, validate the user's attempt to comment ***/
#ifndef NO_SECURITY
user = GetPgUserName();
if (!pg_aggr_ownercheck(user, aggregate, baseoid)) {
if (argument) {
elog(ERROR, "you are not permitted to comment on aggregate '%s' %s '%s'",
aggregate, "with type", argument);
} else {
elog(ERROR, "you are not permitted to comment on aggregate '%s'",
aggregate);
}
}
#endif
/*** Now, attempt to find the actual tuple in pg_aggregate ***/
aggtuple = SearchSysCacheTuple(AGGNAME, PointerGetDatum(aggregate),
ObjectIdGetDatum(baseoid), 0, 0);
if (!HeapTupleIsValid(aggtuple)) {
if (argument) {
elog(ERROR, "aggregate type '%s' does not exist for aggregate '%s'",
argument, aggregate);
} else {
elog(ERROR, "aggregate '%s' does not exist", aggregate);
}
}
oid = aggtuple->t_data->t_oid;
/*** Call CreateComments() to create/drop the comments ***/
CreateComments(oid, comment);
}
/*------------------------------------------------------------------
* CommentProc --
*
* This routine is used to allow a user to provide comments on an
* procedure (function). The procedure is determined by both
* its name and its argument list. The argument list is expected to
* be a series of parsed nodes pointed to by a List object. If the
* comments string is empty, the associated comment is dropped.
*------------------------------------------------------------------
*/
void CommentProc(char *function, List *arguments, char *comment) {
HeapTuple argtuple, functuple;
Oid oid, argoids[8];
char *user, *argument;
int i, argcount;
/*** First, initialize function's argument list with their type oids ***/
argcount = length(arguments);
if (argcount > 0) {
MemSet(argoids, 0, 8 * sizeof(Oid));
for (i = 0; i < argcount; i++) {
argument = strVal(lfirst(arguments));
arguments = lnext(arguments);
if (strcmp(argument, "opaque") == 0) {
argoids[i] = 0;
} else {
argtuple = SearchSysCacheTuple(TYPNAME, PointerGetDatum(argument),
0, 0, 0);
if (!HeapTupleIsValid(argtuple)) {
elog(ERROR, "function argument type '%s' does not exist",
argument);
}
argoids[i] = argtuple->t_data->t_oid;
}
}
}
/*** Now, validate the user's ability to comment on this function ***/
#ifndef NO_SECURITY
user = GetPgUserName();
if (!pg_func_ownercheck(user, function, argcount, argoids)) {
elog(ERROR, "you are not permitted to comment on function '%s'",
function);
}
#endif
/*** Now, find the corresponding oid for this procedure ***/
functuple = SearchSysCacheTuple(PRONAME, PointerGetDatum(function),
Int32GetDatum(argcount),
PointerGetDatum(argoids), 0);
/*** Deallocate our argument oids and check the function tuple ***/
if (!HeapTupleIsValid(functuple)) {
elog(ERROR, "function '%s' with the supplied %s does not exist",
function, "argument list");
}
oid = functuple->t_data->t_oid;
/*** Call CreateComments() to create/drop the comments ***/
CreateComments(oid, comment);
}
/*------------------------------------------------------------------
* CommentOperator --
*
* This routine is used to allow a user to provide comments on an
* operator. The operator for commenting is determined by both
* its name and its argument list which defines the left and right
* hand types the operator will operate on. The argument list is
* expected to be a couple of parse nodes pointed to be a List
* object. If the comments string is empty, the associated comment
* is dropped.
*------------------------------------------------------------------
*/
void CommentOperator(char *opername, List *arguments, char *comment) {
HeapTuple optuple;
Oid oid, leftoid = InvalidOid, rightoid = InvalidOid;
bool defined;
char oprtype = 0, *user, *lefttype = NULL, *righttype = NULL;
/*** Initialize our left and right argument types ***/
if (lfirst(arguments) != NULL) {
lefttype = strVal(lfirst(arguments));
}
if (lsecond(arguments) != NULL) {
righttype = strVal(lsecond(arguments));
}
/*** Attempt to fetch the left oid, if specified ***/
if (lefttype != NULL) {
leftoid = TypeGet(lefttype, &defined);
if (!OidIsValid(leftoid)) {
elog(ERROR, "left type '%s' does not exist", lefttype);
}
}
/*** Attempt to fetch the right oid, if specified ***/
if (righttype != NULL) {
rightoid = TypeGet(righttype, &defined);
if (!OidIsValid(rightoid)) {
elog(ERROR, "right type '%s' does not exist", righttype);
}
}
/*** Determine operator type ***/
if (OidIsValid(leftoid) && (OidIsValid(rightoid))) oprtype = 'b';
else if (OidIsValid(leftoid)) oprtype = 'l';
else if (OidIsValid(rightoid)) oprtype = 'r';
else elog(ERROR, "operator '%s' is of an illegal type'", opername);
/*** Attempt to fetch the operator oid ***/
optuple = SearchSysCacheTupleCopy(OPRNAME, PointerGetDatum(opername),
ObjectIdGetDatum(leftoid),
ObjectIdGetDatum(rightoid),
CharGetDatum(oprtype));
if (!HeapTupleIsValid(optuple)) {
elog(ERROR, "operator '%s' does not exist", opername);
}
oid = optuple->t_data->t_oid;
/*** Valid user's ability to comment on this operator ***/
#ifndef NO_SECURITY
user = GetPgUserName();
if (!pg_ownercheck(user, (char *) ObjectIdGetDatum(oid), OPROID)) {
elog(ERROR, "you are not permitted to comment on operator '%s'",
opername);
}
#endif
/*** Call CreateComments() to create/drop the comments ***/
CreateComments(oid, comment);
}
/*------------------------------------------------------------------
* CommentTrigger --
*
* This routine is used to allow a user to provide comments on a
* trigger event. The trigger for commenting is determined by both
* its name and the relation to which it refers. The arguments to this
* function are the trigger name, the relation name, and the comments
* to add/drop.
*------------------------------------------------------------------
*/
void CommentTrigger(char *trigger, char *relname, char *comment) {
Form_pg_trigger data;
Relation pg_trigger, relation;
HeapTuple triggertuple;
HeapScanDesc scan;
ScanKeyData entry;
Oid oid = InvalidOid;
char *user;
/*** First, validate the user's action ***/
#ifndef NO_SECURITY
user = GetPgUserName();
if (!pg_ownercheck(user, relname, RELNAME)) {
elog(ERROR, "you are not permitted to comment on trigger '%s' %s '%s'",
trigger, "defined for relation", relname);
}
#endif
/*** Now, fetch the trigger oid from pg_trigger ***/
relation = heap_openr(relname, AccessShareLock);
pg_trigger = heap_openr(TriggerRelationName, AccessShareLock);
ScanKeyEntryInitialize(&entry, 0, Anum_pg_trigger_tgrelid,
F_OIDEQ, RelationGetRelid(relation));
scan = heap_beginscan(pg_trigger, 0, SnapshotNow, 1, &entry);
triggertuple = heap_getnext(scan, 0);
while (HeapTupleIsValid(triggertuple)) {
data = (Form_pg_trigger) GETSTRUCT(triggertuple);
if (namestrcmp(&(data->tgname), trigger) == 0) {
oid = triggertuple->t_data->t_oid;
break;
}
triggertuple = heap_getnext(scan, 0);
}
/*** If no trigger exists for the relation specified, notify user ***/
if (oid == InvalidOid) {
elog(ERROR, "trigger '%s' defined for relation '%s' does not exist",
trigger, relname);
}
/*** Create the comments with the pg_trigger oid ***/
CreateComments(oid, comment);
/*** Complete the scan and close any opened relations ***/
heap_endscan(scan);
heap_close(pg_trigger, AccessShareLock);
heap_close(relation, AccessShareLock);
}