mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-08 21:19:27 +02:00
1374 lines
39 KiB
C
1374 lines
39 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* comment.c
|
|
*
|
|
* PostgreSQL object comments utility code.
|
|
*
|
|
* Copyright (c) 1996-2006, PostgreSQL Global Development Group
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/backend/commands/comment.c,v 1.92 2006/10/04 00:29:50 momjian Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/genam.h"
|
|
#include "access/heapam.h"
|
|
#include "catalog/indexing.h"
|
|
#include "catalog/pg_authid.h"
|
|
#include "catalog/pg_cast.h"
|
|
#include "catalog/pg_constraint.h"
|
|
#include "catalog/pg_conversion.h"
|
|
#include "catalog/pg_database.h"
|
|
#include "catalog/pg_description.h"
|
|
#include "catalog/pg_language.h"
|
|
#include "catalog/pg_largeobject.h"
|
|
#include "catalog/pg_namespace.h"
|
|
#include "catalog/pg_opclass.h"
|
|
#include "catalog/pg_operator.h"
|
|
#include "catalog/pg_proc.h"
|
|
#include "catalog/pg_rewrite.h"
|
|
#include "catalog/pg_shdescription.h"
|
|
#include "catalog/pg_tablespace.h"
|
|
#include "catalog/pg_trigger.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "commands/comment.h"
|
|
#include "commands/dbcommands.h"
|
|
#include "commands/tablespace.h"
|
|
#include "miscadmin.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "parser/parse_func.h"
|
|
#include "parser/parse_oper.h"
|
|
#include "parser/parse_type.h"
|
|
#include "utils/acl.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/syscache.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, List *relname, char *comment);
|
|
static void CommentAttribute(List *qualname, char *comment);
|
|
static void CommentDatabase(List *qualname, char *comment);
|
|
static void CommentNamespace(List *qualname, char *comment);
|
|
static void CommentRule(List *qualname, char *comment);
|
|
static void CommentType(List *typename, char *comment);
|
|
static void CommentAggregate(List *aggregate, List *arguments, char *comment);
|
|
static void CommentProc(List *function, List *arguments, char *comment);
|
|
static void CommentOperator(List *opername, List *arguments, char *comment);
|
|
static void CommentTrigger(List *qualname, char *comment);
|
|
static void CommentConstraint(List *qualname, char *comment);
|
|
static void CommentConversion(List *qualname, char *comment);
|
|
static void CommentLanguage(List *qualname, char *comment);
|
|
static void CommentOpClass(List *qualname, List *arguments, char *comment);
|
|
static void CommentLargeObject(List *qualname, char *comment);
|
|
static void CommentCast(List *qualname, List *arguments, char *comment);
|
|
static void CommentTablespace(List *qualname, char *comment);
|
|
static void CommentRole(List *qualname, char *comment);
|
|
|
|
|
|
/*
|
|
* CommentObject --
|
|
*
|
|
* This routine is used to add the associated comment into
|
|
* pg_description for the object specified by the given SQL command.
|
|
*/
|
|
void
|
|
CommentObject(CommentStmt *stmt)
|
|
{
|
|
switch (stmt->objtype)
|
|
{
|
|
case OBJECT_INDEX:
|
|
case OBJECT_SEQUENCE:
|
|
case OBJECT_TABLE:
|
|
case OBJECT_VIEW:
|
|
CommentRelation(stmt->objtype, stmt->objname, stmt->comment);
|
|
break;
|
|
case OBJECT_COLUMN:
|
|
CommentAttribute(stmt->objname, stmt->comment);
|
|
break;
|
|
case OBJECT_DATABASE:
|
|
CommentDatabase(stmt->objname, stmt->comment);
|
|
break;
|
|
case OBJECT_RULE:
|
|
CommentRule(stmt->objname, stmt->comment);
|
|
break;
|
|
case OBJECT_TYPE:
|
|
CommentType(stmt->objname, stmt->comment);
|
|
break;
|
|
case OBJECT_AGGREGATE:
|
|
CommentAggregate(stmt->objname, stmt->objargs, stmt->comment);
|
|
break;
|
|
case OBJECT_FUNCTION:
|
|
CommentProc(stmt->objname, stmt->objargs, stmt->comment);
|
|
break;
|
|
case OBJECT_OPERATOR:
|
|
CommentOperator(stmt->objname, stmt->objargs, stmt->comment);
|
|
break;
|
|
case OBJECT_TRIGGER:
|
|
CommentTrigger(stmt->objname, stmt->comment);
|
|
break;
|
|
case OBJECT_SCHEMA:
|
|
CommentNamespace(stmt->objname, stmt->comment);
|
|
break;
|
|
case OBJECT_CONSTRAINT:
|
|
CommentConstraint(stmt->objname, stmt->comment);
|
|
break;
|
|
case OBJECT_CONVERSION:
|
|
CommentConversion(stmt->objname, stmt->comment);
|
|
break;
|
|
case OBJECT_LANGUAGE:
|
|
CommentLanguage(stmt->objname, stmt->comment);
|
|
break;
|
|
case OBJECT_OPCLASS:
|
|
CommentOpClass(stmt->objname, stmt->objargs, stmt->comment);
|
|
break;
|
|
case OBJECT_LARGEOBJECT:
|
|
CommentLargeObject(stmt->objname, stmt->comment);
|
|
break;
|
|
case OBJECT_CAST:
|
|
CommentCast(stmt->objname, stmt->objargs, stmt->comment);
|
|
break;
|
|
case OBJECT_TABLESPACE:
|
|
CommentTablespace(stmt->objname, stmt->comment);
|
|
break;
|
|
case OBJECT_ROLE:
|
|
CommentRole(stmt->objname, stmt->comment);
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized object type: %d",
|
|
(int) stmt->objtype);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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, char *comment)
|
|
{
|
|
Relation description;
|
|
ScanKeyData skey[3];
|
|
SysScanDesc sd;
|
|
HeapTuple oldtuple;
|
|
HeapTuple newtuple = NULL;
|
|
Datum values[Natts_pg_description];
|
|
char nulls[Natts_pg_description];
|
|
char 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] = ' ';
|
|
replaces[i] = 'r';
|
|
}
|
|
i = 0;
|
|
values[i++] = ObjectIdGetDatum(oid);
|
|
values[i++] = ObjectIdGetDatum(classoid);
|
|
values[i++] = Int32GetDatum(subid);
|
|
values[i++] = DirectFunctionCall1(textin, CStringGetDatum(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 = heap_open(DescriptionRelationId, RowExclusiveLock);
|
|
|
|
sd = systable_beginscan(description, DescriptionObjIndexId, true,
|
|
SnapshotNow, 3, skey);
|
|
|
|
while ((oldtuple = systable_getnext(sd)) != NULL)
|
|
{
|
|
/* Found the old tuple, so delete or update it */
|
|
|
|
if (comment == NULL)
|
|
simple_heap_delete(description, &oldtuple->t_self);
|
|
else
|
|
{
|
|
newtuple = heap_modifytuple(oldtuple, RelationGetDescr(description), values,
|
|
nulls, replaces);
|
|
simple_heap_update(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_formtuple(RelationGetDescr(description),
|
|
values, nulls);
|
|
simple_heap_insert(description, newtuple);
|
|
}
|
|
|
|
/* Update indexes, if necessary */
|
|
if (newtuple != NULL)
|
|
{
|
|
CatalogUpdateIndexes(description, newtuple);
|
|
heap_freetuple(newtuple);
|
|
}
|
|
|
|
/* Done */
|
|
|
|
heap_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, char *comment)
|
|
{
|
|
Relation shdescription;
|
|
ScanKeyData skey[2];
|
|
SysScanDesc sd;
|
|
HeapTuple oldtuple;
|
|
HeapTuple newtuple = NULL;
|
|
Datum values[Natts_pg_shdescription];
|
|
char nulls[Natts_pg_shdescription];
|
|
char 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] = ' ';
|
|
replaces[i] = 'r';
|
|
}
|
|
i = 0;
|
|
values[i++] = ObjectIdGetDatum(oid);
|
|
values[i++] = ObjectIdGetDatum(classoid);
|
|
values[i++] = DirectFunctionCall1(textin, CStringGetDatum(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 = heap_open(SharedDescriptionRelationId, RowExclusiveLock);
|
|
|
|
sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
|
|
SnapshotNow, 2, skey);
|
|
|
|
while ((oldtuple = systable_getnext(sd)) != NULL)
|
|
{
|
|
/* Found the old tuple, so delete or update it */
|
|
|
|
if (comment == NULL)
|
|
simple_heap_delete(shdescription, &oldtuple->t_self);
|
|
else
|
|
{
|
|
newtuple = heap_modifytuple(oldtuple, RelationGetDescr(shdescription),
|
|
values, nulls, replaces);
|
|
simple_heap_update(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_formtuple(RelationGetDescr(shdescription),
|
|
values, nulls);
|
|
simple_heap_insert(shdescription, newtuple);
|
|
}
|
|
|
|
/* Update indexes, if necessary */
|
|
if (newtuple != NULL)
|
|
{
|
|
CatalogUpdateIndexes(shdescription, newtuple);
|
|
heap_freetuple(newtuple);
|
|
}
|
|
|
|
/* Done */
|
|
|
|
heap_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 = heap_open(DescriptionRelationId, RowExclusiveLock);
|
|
|
|
sd = systable_beginscan(description, DescriptionObjIndexId, true,
|
|
SnapshotNow, nkeys, skey);
|
|
|
|
while ((oldtuple = systable_getnext(sd)) != NULL)
|
|
simple_heap_delete(description, &oldtuple->t_self);
|
|
|
|
/* Done */
|
|
|
|
systable_endscan(sd);
|
|
heap_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 = heap_open(SharedDescriptionRelationId, RowExclusiveLock);
|
|
|
|
sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true,
|
|
SnapshotNow, 2, skey);
|
|
|
|
while ((oldtuple = systable_getnext(sd)) != NULL)
|
|
simple_heap_delete(shdescription, &oldtuple->t_self);
|
|
|
|
/* Done */
|
|
|
|
systable_endscan(sd);
|
|
heap_close(shdescription, 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.
|
|
*/
|
|
static void
|
|
CommentRelation(int objtype, List *relname, char *comment)
|
|
{
|
|
Relation relation;
|
|
RangeVar *tgtrel;
|
|
|
|
tgtrel = makeRangeVarFromNameList(relname);
|
|
|
|
/*
|
|
* Open the relation. We do this mainly to acquire a lock that ensures no
|
|
* one else drops the relation before we commit. (If they did, they'd
|
|
* fail to remove the entry we are about to make in pg_description.)
|
|
*/
|
|
relation = relation_openrv(tgtrel, AccessShareLock);
|
|
|
|
/* Check object security */
|
|
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
|
|
RelationGetRelationName(relation));
|
|
|
|
/* Next, verify that the relation type matches the intent */
|
|
|
|
switch (objtype)
|
|
{
|
|
case OBJECT_INDEX:
|
|
if (relation->rd_rel->relkind != RELKIND_INDEX)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("\"%s\" is not an index",
|
|
RelationGetRelationName(relation))));
|
|
break;
|
|
case OBJECT_SEQUENCE:
|
|
if (relation->rd_rel->relkind != RELKIND_SEQUENCE)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("\"%s\" is not a sequence",
|
|
RelationGetRelationName(relation))));
|
|
break;
|
|
case OBJECT_TABLE:
|
|
if (relation->rd_rel->relkind != RELKIND_RELATION)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("\"%s\" is not a table",
|
|
RelationGetRelationName(relation))));
|
|
break;
|
|
case OBJECT_VIEW:
|
|
if (relation->rd_rel->relkind != RELKIND_VIEW)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("\"%s\" is not a view",
|
|
RelationGetRelationName(relation))));
|
|
break;
|
|
}
|
|
|
|
/* Create the comment using the relation's oid */
|
|
CreateComments(RelationGetRelid(relation), RelationRelationId,
|
|
0, comment);
|
|
|
|
/* Done, but hold lock until commit */
|
|
relation_close(relation, NoLock);
|
|
}
|
|
|
|
/*
|
|
* 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 look up the specified
|
|
* attribute. If successful, a comment is added/dropped, else an
|
|
* ereport() exception is thrown. The parameters are the relation
|
|
* and attribute names, and the comment
|
|
*/
|
|
static void
|
|
CommentAttribute(List *qualname, char *comment)
|
|
{
|
|
int nnames;
|
|
List *relname;
|
|
char *attrname;
|
|
RangeVar *rel;
|
|
Relation relation;
|
|
AttrNumber attnum;
|
|
|
|
/* Separate relname and attr name */
|
|
nnames = list_length(qualname);
|
|
if (nnames < 2) /* parser messed up */
|
|
elog(ERROR, "must specify relation and attribute");
|
|
relname = list_truncate(list_copy(qualname), nnames - 1);
|
|
attrname = strVal(lfirst(list_tail(qualname)));
|
|
|
|
/* Open the containing relation to ensure it won't go away meanwhile */
|
|
rel = makeRangeVarFromNameList(relname);
|
|
relation = relation_openrv(rel, AccessShareLock);
|
|
|
|
/* Check object security */
|
|
|
|
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
|
|
RelationGetRelationName(relation));
|
|
|
|
/* Now, fetch the attribute number from the system cache */
|
|
|
|
attnum = get_attnum(RelationGetRelid(relation), attrname);
|
|
if (attnum == InvalidAttrNumber)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
|
errmsg("column \"%s\" of relation \"%s\" does not exist",
|
|
attrname, RelationGetRelationName(relation))));
|
|
|
|
/* Create the comment using the relation's oid */
|
|
CreateComments(RelationGetRelid(relation), RelationRelationId,
|
|
(int32) attnum, comment);
|
|
|
|
/* Done, but hold lock until commit */
|
|
|
|
relation_close(relation, NoLock);
|
|
}
|
|
|
|
/*
|
|
* 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 successful, will then
|
|
* attempt to find the oid of the database specified. Once found,
|
|
* a comment is added/dropped using the CreateSharedComments() routine.
|
|
*/
|
|
static void
|
|
CommentDatabase(List *qualname, char *comment)
|
|
{
|
|
char *database;
|
|
Oid oid;
|
|
|
|
if (list_length(qualname) != 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("database name may not be qualified")));
|
|
database = strVal(linitial(qualname));
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
/* First get the database OID */
|
|
oid = get_database_oid(database);
|
|
if (!OidIsValid(oid))
|
|
{
|
|
ereport(WARNING,
|
|
(errcode(ERRCODE_UNDEFINED_DATABASE),
|
|
errmsg("database \"%s\" does not exist", database)));
|
|
return;
|
|
}
|
|
|
|
/* Check object security */
|
|
if (!pg_database_ownercheck(oid, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
|
|
database);
|
|
|
|
/* Call CreateSharedComments() to create/drop the comments */
|
|
CreateSharedComments(oid, DatabaseRelationId, comment);
|
|
}
|
|
|
|
/*
|
|
* CommentTablespace --
|
|
*
|
|
* This routine is used to add/drop any user-comments a user might
|
|
* have regarding a tablespace. The tablepace is specified by name
|
|
* and, if found, and the user has appropriate permissions, a
|
|
* comment will be added/dropped using the CreateSharedComments() routine.
|
|
*
|
|
*/
|
|
static void
|
|
CommentTablespace(List *qualname, char *comment)
|
|
{
|
|
char *tablespace;
|
|
Oid oid;
|
|
|
|
if (list_length(qualname) != 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("tablespace name may not be qualified")));
|
|
tablespace = strVal(linitial(qualname));
|
|
|
|
oid = get_tablespace_oid(tablespace);
|
|
if (!OidIsValid(oid))
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("tablespace \"%s\" does not exist", tablespace)));
|
|
return;
|
|
}
|
|
|
|
/* Check object security */
|
|
if (!pg_tablespace_ownercheck(oid, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, tablespace);
|
|
|
|
/* Call CreateSharedComments() to create/drop the comments */
|
|
CreateSharedComments(oid, TableSpaceRelationId, comment);
|
|
}
|
|
|
|
/*
|
|
* CommentRole --
|
|
*
|
|
* This routine is used to add/drop any user-comments a user might
|
|
* have regarding a role. The role is specified by name
|
|
* and, if found, and the user has appropriate permissions, a
|
|
* comment will be added/dropped using the CreateSharedComments() routine.
|
|
*/
|
|
static void
|
|
CommentRole(List *qualname, char *comment)
|
|
{
|
|
char *role;
|
|
Oid oid;
|
|
|
|
if (list_length(qualname) != 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("role name may not be qualified")));
|
|
role = strVal(linitial(qualname));
|
|
|
|
oid = get_roleid_checked(role);
|
|
|
|
/* Check object security */
|
|
if (!has_privs_of_role(GetUserId(), oid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be member of role \"%s\" to comment upon it", role)));
|
|
|
|
/* Call CreateSharedComments() to create/drop the comments */
|
|
CreateSharedComments(oid, AuthIdRelationId, comment);
|
|
}
|
|
|
|
/*
|
|
* CommentNamespace --
|
|
*
|
|
* This routine is used to add/drop any user-comments a user might
|
|
* have regarding the specified namespace. The routine will check
|
|
* security for owner permissions, and, if successful, will then
|
|
* attempt to find the oid of the namespace specified. Once found,
|
|
* a comment is added/dropped using the CreateComments() routine.
|
|
*/
|
|
static void
|
|
CommentNamespace(List *qualname, char *comment)
|
|
{
|
|
Oid oid;
|
|
char *namespace;
|
|
|
|
if (list_length(qualname) != 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("schema name may not be qualified")));
|
|
namespace = strVal(linitial(qualname));
|
|
|
|
oid = GetSysCacheOid(NAMESPACENAME,
|
|
CStringGetDatum(namespace),
|
|
0, 0, 0);
|
|
if (!OidIsValid(oid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_SCHEMA),
|
|
errmsg("schema \"%s\" does not exist", namespace)));
|
|
|
|
/* Check object security */
|
|
if (!pg_namespace_ownercheck(oid, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE,
|
|
namespace);
|
|
|
|
/* Call CreateComments() to create/drop the comments */
|
|
CreateComments(oid, NamespaceRelationId, 0, comment);
|
|
}
|
|
|
|
/*
|
|
* CommentRule --
|
|
*
|
|
* This routine is used to add/drop any user-comments a user might
|
|
* have regarding a specified RULE. The rule for commenting is determined by
|
|
* both its name and the relation to which it refers. The arguments to this
|
|
* function are the rule name and relation name (merged into a qualified
|
|
* name), and the comment to add/drop.
|
|
*
|
|
* Before PG 7.3, rules had unique names across the whole database, and so
|
|
* the syntax was just COMMENT ON RULE rulename, with no relation name.
|
|
* For purposes of backwards compatibility, we support that as long as there
|
|
* is only one rule by the specified name in the database.
|
|
*/
|
|
static void
|
|
CommentRule(List *qualname, char *comment)
|
|
{
|
|
int nnames;
|
|
List *relname;
|
|
char *rulename;
|
|
RangeVar *rel;
|
|
Relation relation;
|
|
HeapTuple tuple;
|
|
Oid reloid;
|
|
Oid ruleoid;
|
|
|
|
/* Separate relname and trig name */
|
|
nnames = list_length(qualname);
|
|
if (nnames == 1)
|
|
{
|
|
/* Old-style: only a rule name is given */
|
|
Relation RewriteRelation;
|
|
HeapScanDesc scanDesc;
|
|
ScanKeyData scanKeyData;
|
|
|
|
rulename = strVal(linitial(qualname));
|
|
|
|
/* Search pg_rewrite for such a rule */
|
|
ScanKeyInit(&scanKeyData,
|
|
Anum_pg_rewrite_rulename,
|
|
BTEqualStrategyNumber, F_NAMEEQ,
|
|
PointerGetDatum(rulename));
|
|
|
|
RewriteRelation = heap_open(RewriteRelationId, AccessShareLock);
|
|
scanDesc = heap_beginscan(RewriteRelation, SnapshotNow,
|
|
1, &scanKeyData);
|
|
|
|
tuple = heap_getnext(scanDesc, ForwardScanDirection);
|
|
if (HeapTupleIsValid(tuple))
|
|
{
|
|
reloid = ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class;
|
|
ruleoid = HeapTupleGetOid(tuple);
|
|
}
|
|
else
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("rule \"%s\" does not exist", rulename)));
|
|
reloid = ruleoid = 0; /* keep compiler quiet */
|
|
}
|
|
|
|
if (HeapTupleIsValid(tuple = heap_getnext(scanDesc,
|
|
ForwardScanDirection)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("there are multiple rules named \"%s\"", rulename),
|
|
errhint("Specify a relation name as well as a rule name.")));
|
|
|
|
heap_endscan(scanDesc);
|
|
heap_close(RewriteRelation, AccessShareLock);
|
|
|
|
/* Open the owning relation to ensure it won't go away meanwhile */
|
|
relation = heap_open(reloid, AccessShareLock);
|
|
}
|
|
else
|
|
{
|
|
/* New-style: rule and relname both provided */
|
|
Assert(nnames >= 2);
|
|
relname = list_truncate(list_copy(qualname), nnames - 1);
|
|
rulename = strVal(lfirst(list_tail(qualname)));
|
|
|
|
/* Open the owning relation to ensure it won't go away meanwhile */
|
|
rel = makeRangeVarFromNameList(relname);
|
|
relation = heap_openrv(rel, AccessShareLock);
|
|
reloid = RelationGetRelid(relation);
|
|
|
|
/* Find the rule's pg_rewrite tuple, get its OID */
|
|
tuple = SearchSysCache(RULERELNAME,
|
|
ObjectIdGetDatum(reloid),
|
|
PointerGetDatum(rulename),
|
|
0, 0);
|
|
if (!HeapTupleIsValid(tuple))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("rule \"%s\" for relation \"%s\" does not exist",
|
|
rulename, RelationGetRelationName(relation))));
|
|
Assert(reloid == ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class);
|
|
ruleoid = HeapTupleGetOid(tuple);
|
|
ReleaseSysCache(tuple);
|
|
}
|
|
|
|
/* Check object security */
|
|
if (!pg_class_ownercheck(reloid, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
|
|
get_rel_name(reloid));
|
|
|
|
/* Call CreateComments() to create/drop the comments */
|
|
CreateComments(ruleoid, RewriteRelationId, 0, comment);
|
|
|
|
heap_close(relation, NoLock);
|
|
}
|
|
|
|
/*
|
|
* 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 parameters to this routine.
|
|
*/
|
|
static void
|
|
CommentType(List *typename, char *comment)
|
|
{
|
|
TypeName *tname;
|
|
Oid oid;
|
|
|
|
/* XXX a bit of a crock; should accept TypeName in COMMENT syntax */
|
|
tname = makeTypeNameFromNameList(typename);
|
|
|
|
/* Find the type's oid */
|
|
|
|
oid = typenameTypeId(NULL, tname);
|
|
|
|
/* Check object security */
|
|
|
|
if (!pg_type_ownercheck(oid, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
|
|
TypeNameToString(tname));
|
|
|
|
/* Call CreateComments() to create/drop the comments */
|
|
CreateComments(oid, TypeRelationId, 0, 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(s).
|
|
*/
|
|
static void
|
|
CommentAggregate(List *aggregate, List *arguments, char *comment)
|
|
{
|
|
Oid oid;
|
|
|
|
/* Look up function and make sure it's an aggregate */
|
|
oid = LookupAggNameTypeNames(aggregate, arguments, false);
|
|
|
|
/* Next, validate the user's attempt to comment */
|
|
if (!pg_proc_ownercheck(oid, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
|
|
NameListToString(aggregate));
|
|
|
|
/* Call CreateComments() to create/drop the comments */
|
|
CreateComments(oid, ProcedureRelationId, 0, 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.
|
|
*/
|
|
static void
|
|
CommentProc(List *function, List *arguments, char *comment)
|
|
{
|
|
Oid oid;
|
|
|
|
/* Look up the procedure */
|
|
|
|
oid = LookupFuncNameTypeNames(function, arguments, false);
|
|
|
|
/* Now, validate the user's ability to comment on this function */
|
|
|
|
if (!pg_proc_ownercheck(oid, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
|
|
NameListToString(function));
|
|
|
|
/* Call CreateComments() to create/drop the comments */
|
|
CreateComments(oid, ProcedureRelationId, 0, 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.
|
|
*/
|
|
static void
|
|
CommentOperator(List *opername, List *arguments, char *comment)
|
|
{
|
|
TypeName *typenode1 = (TypeName *) linitial(arguments);
|
|
TypeName *typenode2 = (TypeName *) lsecond(arguments);
|
|
Oid oid;
|
|
|
|
/* Look up the operator */
|
|
oid = LookupOperNameTypeNames(NULL, opername,
|
|
typenode1, typenode2,
|
|
false, -1);
|
|
|
|
/* Check user's privilege to comment on this operator */
|
|
if (!pg_oper_ownercheck(oid, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPER,
|
|
NameListToString(opername));
|
|
|
|
/* Call CreateComments() to create/drop the comments */
|
|
CreateComments(oid, OperatorRelationId, 0, 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 and relation name (merged into a qualified
|
|
* name), and the comment to add/drop.
|
|
*/
|
|
static void
|
|
CommentTrigger(List *qualname, char *comment)
|
|
{
|
|
int nnames;
|
|
List *relname;
|
|
char *trigname;
|
|
RangeVar *rel;
|
|
Relation pg_trigger,
|
|
relation;
|
|
HeapTuple triggertuple;
|
|
SysScanDesc scan;
|
|
ScanKeyData entry[2];
|
|
Oid oid;
|
|
|
|
/* Separate relname and trig name */
|
|
nnames = list_length(qualname);
|
|
if (nnames < 2) /* parser messed up */
|
|
elog(ERROR, "must specify relation and trigger");
|
|
relname = list_truncate(list_copy(qualname), nnames - 1);
|
|
trigname = strVal(lfirst(list_tail(qualname)));
|
|
|
|
/* Open the owning relation to ensure it won't go away meanwhile */
|
|
rel = makeRangeVarFromNameList(relname);
|
|
relation = heap_openrv(rel, AccessShareLock);
|
|
|
|
/* Check object security */
|
|
|
|
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
|
|
RelationGetRelationName(relation));
|
|
|
|
/*
|
|
* Fetch the trigger tuple from pg_trigger. There can be only one because
|
|
* of the unique index.
|
|
*/
|
|
pg_trigger = heap_open(TriggerRelationId, AccessShareLock);
|
|
ScanKeyInit(&entry[0],
|
|
Anum_pg_trigger_tgrelid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(RelationGetRelid(relation)));
|
|
ScanKeyInit(&entry[1],
|
|
Anum_pg_trigger_tgname,
|
|
BTEqualStrategyNumber, F_NAMEEQ,
|
|
CStringGetDatum(trigname));
|
|
scan = systable_beginscan(pg_trigger, TriggerRelidNameIndexId, true,
|
|
SnapshotNow, 2, entry);
|
|
triggertuple = systable_getnext(scan);
|
|
|
|
/* If no trigger exists for the relation specified, notify user */
|
|
|
|
if (!HeapTupleIsValid(triggertuple))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("trigger \"%s\" for table \"%s\" does not exist",
|
|
trigname, RelationGetRelationName(relation))));
|
|
|
|
oid = HeapTupleGetOid(triggertuple);
|
|
|
|
systable_endscan(scan);
|
|
|
|
/* Call CreateComments() to create/drop the comments */
|
|
CreateComments(oid, TriggerRelationId, 0, comment);
|
|
|
|
/* Done, but hold lock on relation */
|
|
|
|
heap_close(pg_trigger, AccessShareLock);
|
|
heap_close(relation, NoLock);
|
|
}
|
|
|
|
|
|
/*
|
|
* CommentConstraint --
|
|
*
|
|
* Enable commenting on constraints held within the pg_constraint
|
|
* table. A qualified name is required as constraint names are
|
|
* unique per relation.
|
|
*/
|
|
static void
|
|
CommentConstraint(List *qualname, char *comment)
|
|
{
|
|
int nnames;
|
|
List *relName;
|
|
char *conName;
|
|
RangeVar *rel;
|
|
Relation pg_constraint,
|
|
relation;
|
|
HeapTuple tuple;
|
|
SysScanDesc scan;
|
|
ScanKeyData skey[1];
|
|
Oid conOid = InvalidOid;
|
|
|
|
/* Separate relname and constraint name */
|
|
nnames = list_length(qualname);
|
|
if (nnames < 2) /* parser messed up */
|
|
elog(ERROR, "must specify relation and constraint");
|
|
relName = list_truncate(list_copy(qualname), nnames - 1);
|
|
conName = strVal(lfirst(list_tail(qualname)));
|
|
|
|
/* Open the owning relation to ensure it won't go away meanwhile */
|
|
rel = makeRangeVarFromNameList(relName);
|
|
relation = heap_openrv(rel, AccessShareLock);
|
|
|
|
/* Check object security */
|
|
|
|
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
|
|
RelationGetRelationName(relation));
|
|
|
|
/*
|
|
* Fetch the constraint tuple from pg_constraint. There may be more than
|
|
* one match, because constraints are not required to have unique names;
|
|
* if so, error out.
|
|
*/
|
|
pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
|
|
|
|
ScanKeyInit(&skey[0],
|
|
Anum_pg_constraint_conrelid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(RelationGetRelid(relation)));
|
|
|
|
scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
|
|
SnapshotNow, 1, skey);
|
|
|
|
while (HeapTupleIsValid(tuple = systable_getnext(scan)))
|
|
{
|
|
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
|
|
|
|
if (strcmp(NameStr(con->conname), conName) == 0)
|
|
{
|
|
if (OidIsValid(conOid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("table \"%s\" has multiple constraints named \"%s\"",
|
|
RelationGetRelationName(relation), conName)));
|
|
conOid = HeapTupleGetOid(tuple);
|
|
}
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
|
|
/* If no constraint exists for the relation specified, notify user */
|
|
if (!OidIsValid(conOid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("constraint \"%s\" for table \"%s\" does not exist",
|
|
conName, RelationGetRelationName(relation))));
|
|
|
|
/* Call CreateComments() to create/drop the comments */
|
|
CreateComments(conOid, ConstraintRelationId, 0, comment);
|
|
|
|
/* Done, but hold lock on relation */
|
|
heap_close(pg_constraint, AccessShareLock);
|
|
heap_close(relation, NoLock);
|
|
}
|
|
|
|
/*
|
|
* CommentConversion --
|
|
*
|
|
* This routine is used to add/drop any user-comments a user might
|
|
* have regarding a CONVERSION. The conversion is specified by name
|
|
* and, if found, and the user has appropriate permissions, a
|
|
* comment will be added/dropped using the CreateComments() routine.
|
|
* The conversion's name and the comment are the parameters to this routine.
|
|
*/
|
|
static void
|
|
CommentConversion(List *qualname, char *comment)
|
|
{
|
|
Oid conversionOid;
|
|
|
|
conversionOid = FindConversionByName(qualname);
|
|
if (!OidIsValid(conversionOid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("conversion \"%s\" does not exist",
|
|
NameListToString(qualname))));
|
|
|
|
/* Check object security */
|
|
if (!pg_conversion_ownercheck(conversionOid, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION,
|
|
NameListToString(qualname));
|
|
|
|
/* Call CreateComments() to create/drop the comments */
|
|
CreateComments(conversionOid, ConversionRelationId, 0, comment);
|
|
}
|
|
|
|
/*
|
|
* CommentLanguage --
|
|
*
|
|
* This routine is used to add/drop any user-comments a user might
|
|
* have regarding a LANGUAGE. The language is specified by name
|
|
* and, if found, and the user has appropriate permissions, a
|
|
* comment will be added/dropped using the CreateComments() routine.
|
|
* The language's name and the comment are the parameters to this routine.
|
|
*/
|
|
static void
|
|
CommentLanguage(List *qualname, char *comment)
|
|
{
|
|
Oid oid;
|
|
char *language;
|
|
|
|
if (list_length(qualname) != 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("language name may not be qualified")));
|
|
language = strVal(linitial(qualname));
|
|
|
|
oid = GetSysCacheOid(LANGNAME,
|
|
CStringGetDatum(language),
|
|
0, 0, 0);
|
|
if (!OidIsValid(oid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_SCHEMA),
|
|
errmsg("language \"%s\" does not exist", language)));
|
|
|
|
/* Check object security */
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to comment on procedural language")));
|
|
|
|
/* Call CreateComments() to create/drop the comments */
|
|
CreateComments(oid, LanguageRelationId, 0, comment);
|
|
}
|
|
|
|
/*
|
|
* CommentOpClass --
|
|
*
|
|
* This routine is used to allow a user to provide comments on an
|
|
* operator class. The operator class for commenting is determined by both
|
|
* its name and its argument list which defines the index method
|
|
* the operator class is used for. The argument list is expected to contain
|
|
* a single name (represented as a string Value node).
|
|
*/
|
|
static void
|
|
CommentOpClass(List *qualname, List *arguments, char *comment)
|
|
{
|
|
char *amname;
|
|
char *schemaname;
|
|
char *opcname;
|
|
Oid amID;
|
|
Oid opcID;
|
|
HeapTuple tuple;
|
|
|
|
Assert(list_length(arguments) == 1);
|
|
amname = strVal(linitial(arguments));
|
|
|
|
/*
|
|
* Get the access method's OID.
|
|
*/
|
|
amID = GetSysCacheOid(AMNAME,
|
|
CStringGetDatum(amname),
|
|
0, 0, 0);
|
|
if (!OidIsValid(amID))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("access method \"%s\" does not exist",
|
|
amname)));
|
|
|
|
/*
|
|
* Look up the opclass.
|
|
*/
|
|
|
|
/* deconstruct the name list */
|
|
DeconstructQualifiedName(qualname, &schemaname, &opcname);
|
|
|
|
if (schemaname)
|
|
{
|
|
/* Look in specific schema only */
|
|
Oid namespaceId;
|
|
|
|
namespaceId = LookupExplicitNamespace(schemaname);
|
|
tuple = SearchSysCache(CLAAMNAMENSP,
|
|
ObjectIdGetDatum(amID),
|
|
PointerGetDatum(opcname),
|
|
ObjectIdGetDatum(namespaceId),
|
|
0);
|
|
}
|
|
else
|
|
{
|
|
/* Unqualified opclass name, so search the search path */
|
|
opcID = OpclassnameGetOpcid(amID, opcname);
|
|
if (!OidIsValid(opcID))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
|
|
opcname, amname)));
|
|
tuple = SearchSysCache(CLAOID,
|
|
ObjectIdGetDatum(opcID),
|
|
0, 0, 0);
|
|
}
|
|
|
|
if (!HeapTupleIsValid(tuple))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
|
|
NameListToString(qualname), amname)));
|
|
|
|
opcID = HeapTupleGetOid(tuple);
|
|
|
|
/* Permission check: must own opclass */
|
|
if (!pg_opclass_ownercheck(opcID, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS,
|
|
NameListToString(qualname));
|
|
|
|
ReleaseSysCache(tuple);
|
|
|
|
/* Call CreateComments() to create/drop the comments */
|
|
CreateComments(opcID, OperatorClassRelationId, 0, comment);
|
|
}
|
|
|
|
/*
|
|
* CommentLargeObject --
|
|
*
|
|
* This routine is used to add/drop any user-comments a user might
|
|
* have regarding a LARGE OBJECT. The large object is specified by OID
|
|
* and, if found, and the user has appropriate permissions, a
|
|
* comment will be added/dropped using the CreateComments() routine.
|
|
* The large object's OID and the comment are the parameters to this routine.
|
|
*/
|
|
static void
|
|
CommentLargeObject(List *qualname, char *comment)
|
|
{
|
|
Oid loid;
|
|
Node *node;
|
|
|
|
Assert(list_length(qualname) == 1);
|
|
node = (Node *) linitial(qualname);
|
|
|
|
switch (nodeTag(node))
|
|
{
|
|
case T_Integer:
|
|
loid = intVal(node);
|
|
break;
|
|
case T_Float:
|
|
|
|
/*
|
|
* Values too large for int4 will be represented as Float
|
|
* constants by the lexer. Accept these if they are valid OID
|
|
* strings.
|
|
*/
|
|
loid = DatumGetObjectId(DirectFunctionCall1(oidin,
|
|
CStringGetDatum(strVal(node))));
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized node type: %d",
|
|
(int) nodeTag(node));
|
|
/* keep compiler quiet */
|
|
loid = InvalidOid;
|
|
}
|
|
|
|
/* check that the large object exists */
|
|
if (!LargeObjectExists(loid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("large object %u does not exist", loid)));
|
|
|
|
/* Call CreateComments() to create/drop the comments */
|
|
CreateComments(loid, LargeObjectRelationId, 0, comment);
|
|
}
|
|
|
|
/*
|
|
* CommentCast --
|
|
*
|
|
* This routine is used to add/drop any user-comments a user might
|
|
* have regarding a CAST. The cast is specified by source and destination types
|
|
* and, if found, and the user has appropriate permissions, a
|
|
* comment will be added/dropped using the CreateComments() routine.
|
|
* The cast's source type is passed as the "name", the destination type
|
|
* as the "arguments".
|
|
*/
|
|
static void
|
|
CommentCast(List *qualname, List *arguments, char *comment)
|
|
{
|
|
TypeName *sourcetype;
|
|
TypeName *targettype;
|
|
Oid sourcetypeid;
|
|
Oid targettypeid;
|
|
HeapTuple tuple;
|
|
Oid castOid;
|
|
|
|
Assert(list_length(qualname) == 1);
|
|
sourcetype = (TypeName *) linitial(qualname);
|
|
Assert(IsA(sourcetype, TypeName));
|
|
Assert(list_length(arguments) == 1);
|
|
targettype = (TypeName *) linitial(arguments);
|
|
Assert(IsA(targettype, TypeName));
|
|
|
|
sourcetypeid = typenameTypeId(NULL, sourcetype);
|
|
targettypeid = typenameTypeId(NULL, targettype);
|
|
|
|
tuple = SearchSysCache(CASTSOURCETARGET,
|
|
ObjectIdGetDatum(sourcetypeid),
|
|
ObjectIdGetDatum(targettypeid),
|
|
0, 0);
|
|
if (!HeapTupleIsValid(tuple))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("cast from type %s to type %s does not exist",
|
|
TypeNameToString(sourcetype),
|
|
TypeNameToString(targettype))));
|
|
|
|
/* Get the OID of the cast */
|
|
castOid = HeapTupleGetOid(tuple);
|
|
|
|
/* Permission check */
|
|
if (!pg_type_ownercheck(sourcetypeid, GetUserId())
|
|
&& !pg_type_ownercheck(targettypeid, GetUserId()))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be owner of type %s or type %s",
|
|
TypeNameToString(sourcetype),
|
|
TypeNameToString(targettype))));
|
|
|
|
ReleaseSysCache(tuple);
|
|
|
|
/* Call CreateComments() to create/drop the comments */
|
|
CreateComments(castOid, CastRelationId, 0, comment);
|
|
}
|