From 42ce74bf17478cc8a8323601deaa63950dda9923 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Fri, 21 Nov 2003 22:32:49 +0000
Subject: [PATCH] COMMENT ON casts, conversions, languages, operator classes,
 and large objects.  Dump all these in pg_dump; also add code to pg_dump
 user-defined conversions.  Make psql's large object code rely on the backend
 for inserting/deleting LOB comments, instead of trying to hack pg_description
 directly.  Documentation and regression tests added.

Christopher Kings-Lynne, code reviewed by Tom
---
 doc/src/sgml/ref/comment.sgml                 |  73 +++-
 src/backend/catalog/aclchk.c                  |  31 +-
 src/backend/commands/comment.c                | 322 +++++++++++++++++-
 src/backend/commands/functioncmds.c           |   5 +-
 src/backend/parser/gram.y                     |  59 +++-
 src/backend/parser/keywords.c                 |   4 +-
 src/backend/storage/large_object/inv_api.c    |  14 +-
 src/bin/pg_dump/common.c                      |  15 +-
 src/bin/pg_dump/pg_dump.c                     | 238 ++++++++++++-
 src/bin/pg_dump/pg_dump.h                     |  13 +-
 src/bin/psql/large_obj.c                      |  28 +-
 src/include/nodes/parsenodes.h                |   3 +-
 src/include/utils/acl.h                       |   3 +-
 src/test/regress/expected/alter_table.out     |   4 +
 src/test/regress/expected/conversion.out      |   5 +
 .../regress/expected/create_aggregate.out     |   9 +
 src/test/regress/expected/create_index.out    |   5 +
 src/test/regress/expected/create_operator.out |   5 +
 src/test/regress/expected/create_type.out     |   5 +
 src/test/regress/expected/create_view.out     |   5 +
 src/test/regress/expected/foreign_key.out     |   5 +
 src/test/regress/expected/plpgsql.out         |   5 +
 src/test/regress/expected/rules.out           |   5 +
 src/test/regress/expected/sequence.out        |   5 +
 src/test/regress/expected/triggers.out        |   5 +
 src/test/regress/sql/alter_table.sql          |   4 +
 src/test/regress/sql/conversion.sql           |   4 +
 src/test/regress/sql/create_aggregate.sql     |   8 +
 src/test/regress/sql/create_index.sql         |   5 +
 src/test/regress/sql/create_operator.sql      |   6 +
 src/test/regress/sql/create_type.sql          |   5 +
 src/test/regress/sql/create_view.sql          |   5 +
 src/test/regress/sql/foreign_key.sql          |   5 +
 src/test/regress/sql/plpgsql.sql              |   6 +-
 src/test/regress/sql/rules.sql                |   5 +-
 src/test/regress/sql/sequence.sql             |   5 +
 src/test/regress/sql/triggers.sql             |   5 +
 37 files changed, 879 insertions(+), 55 deletions(-)

diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index 7e0f4318bfd..7e1ff18d5e4 100644
--- a/doc/src/sgml/ref/comment.sgml
+++ b/doc/src/sgml/ref/comment.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/comment.sgml,v 1.23 2003/09/09 18:28:52 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/comment.sgml,v 1.24 2003/11/21 22:32:48 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -25,12 +25,17 @@ COMMENT ON
   TABLE <replaceable class="PARAMETER">object_name</replaceable> |
   COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
   AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> (<replaceable class="PARAMETER">agg_type</replaceable>) |
+  CAST (<replaceable>sourcetype</replaceable> AS <replaceable>targettype</replaceable>) |
   CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ON <replaceable class="PARAMETER">table_name</replaceable> |
+  CONVERSION <replaceable class="PARAMETER">object_name</replaceable> |
   DATABASE <replaceable class="PARAMETER">object_name</replaceable> |
   DOMAIN <replaceable class="PARAMETER">object_name</replaceable> |
   FUNCTION <replaceable class="PARAMETER">func_name</replaceable> (<replaceable class="PARAMETER">arg1_type</replaceable>, <replaceable class="PARAMETER">arg2_type</replaceable>, ...) |
   INDEX <replaceable class="PARAMETER">object_name</replaceable> |
+  LARGE OBJECT <replaceable class="PARAMETER">large_object_oid</replaceable> |
   OPERATOR <replaceable class="PARAMETER">op</replaceable> (<replaceable class="PARAMETER">leftoperand_type</replaceable>, <replaceable class="PARAMETER">rightoperand_type</replaceable>) |
+  OPERATOR CLASS <replaceable class="PARAMETER">object_name</replaceable> USING <replaceable class="parameter">index_method</replaceable> |
+  [ PROCEDURAL ] LANGUAGE <replaceable class="PARAMETER">object_name</replaceable> |
   RULE <replaceable class="PARAMETER">rule_name</replaceable> ON <replaceable class="PARAMETER">table_name</replaceable> |
   SCHEMA <replaceable class="PARAMETER">object_name</replaceable> |
   SEQUENCE <replaceable class="PARAMETER">object_name</replaceable> |
@@ -70,7 +75,7 @@ COMMENT ON
    <varlistentry>
     <term><replaceable class="parameter">object_name</replaceable></term>
     <term><replaceable class="parameter">table_name.column_name</replaceable></term>
-    <term><replaceable class="parameter">aggname</replaceable></term>
+    <term><replaceable class="parameter">agg_name</replaceable></term>
     <term><replaceable class="parameter">constraint_name</replaceable></term>
     <term><replaceable class="parameter">func_name</replaceable></term>
     <term><replaceable class="parameter">op</replaceable></term>
@@ -78,13 +83,60 @@ COMMENT ON
     <term><replaceable class="parameter">trigger_name</replaceable></term>
     <listitem>
      <para>
-      The name of the object to be be commented.  Names of tables,
-      aggregates, domains, functions, indexes, operators, sequences,
-      types, and views may be schema-qualified.
+      The name of the object to be commented.  Names of tables,
+      aggregates, domains, functions, indexes, operators, operator classes,
+      sequences, types, and views may be schema-qualified.
      </para>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="parameter">agg_type</replaceable></term>
+    <listitem>
+     <para>
+      The argument data type of the aggregate function, or
+      <literal>*</literal> if the function accepts any data type.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">large_object_oid</replaceable></term>
+    <listitem>
+     <para>
+      The OID of the large object.
+     </para>
+    </listitem>
+   </varlistentry>
+
+    <varlistentry>
+     <term><literal>PROCEDURAL</literal></term>
+
+     <listitem>
+      <para>
+       This is a noise word.
+      </para>
+     </listitem>
+    </varlistentry>
+   
+   <varlistentry>
+     <term><replaceable>sourcetype</replaceable></term>
+     <listitem>
+      <para>
+       The name of the source data type of the cast.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable>targettype</replaceable></term>
+     <listitem>
+      <para>
+       The name of the target data type of the cast.
+      </para>
+     </listitem>
+    </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">text</replaceable></term>
     <listitem>
@@ -93,12 +145,18 @@ COMMENT ON
      </para>
     </listitem>
    </varlistentry>
+    
   </variablelist>
  </refsect1>
 
  <refsect1>
   <title>Notes</title>
 
+  <para>
+   A comment for a database can only be created in that database,
+   and will only be visible in that database, not in other databases.
+  </para>
+
   <para>
    There is presently no security mechanism for comments: any user
    connected to a database can see all the comments for objects in
@@ -130,13 +188,18 @@ COMMENT ON TABLE mytable IS NULL;
 
 <programlisting>
 COMMENT ON AGGREGATE my_aggregate (double precision) IS 'Computes sample variance';
+COMMENT ON CAST (text AS int4) IS 'Allow casts from text to int4';
 COMMENT ON COLUMN my_table.my_column IS 'Employee ID number';
+COMMENT ON CONVERSION my_conv IS 'Conversion to Unicode';
 COMMENT ON DATABASE my_database IS 'Development Database';
 COMMENT ON DOMAIN my_domain IS 'Email Address Domain';
 COMMENT ON FUNCTION my_function (timestamp) IS 'Returns Roman Numeral';
 COMMENT ON INDEX my_index IS 'Enforces uniqueness on employee ID';
+COMMENT ON LANGUAGE plpython IS 'Python support for stored procedures';
+COMMENT ON LARGE OBJECT 346344 IS 'Planning document';
 COMMENT ON OPERATOR ^ (text, text) IS 'Performs intersection of two texts';
 COMMENT ON OPERATOR ^ (NONE, text) IS 'This is a prefix operator on text';
+COMMENT ON OPERATOR CLASS int4ops USING btree IS '4 byte integer operators for btrees';
 COMMENT ON RULE my_rule ON my_table IS 'Logs updates of employee records';
 COMMENT ON SCHEMA my_schema IS 'Departmental data';
 COMMENT ON SEQUENCE my_sequence IS 'Used to generate primary keys';
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 7534750c99e..93103c16f89 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/catalog/aclchk.c,v 1.93 2003/11/12 21:15:48 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/catalog/aclchk.c,v 1.94 2003/11/21 22:32:48 tgl Exp $
  *
  * NOTES
  *	  See acl.h.
@@ -22,6 +22,7 @@
 #include "catalog/catname.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_conversion.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_group.h"
 #include "catalog/pg_language.h"
@@ -1551,3 +1552,31 @@ pg_database_ownercheck(Oid db_oid, AclId userid)
 
 	return userid == dba;
 }
+
+/*
+ * Ownership check for a conversion (specified by OID).
+ */
+bool
+pg_conversion_ownercheck(Oid conv_oid, AclId userid)
+{
+	HeapTuple	tuple;
+	AclId		owner_id;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(userid))
+		return true;
+
+	tuple = SearchSysCache(CONOID,
+						   ObjectIdGetDatum(conv_oid),
+						   0, 0, 0);
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("conversion with OID %u does not exist", conv_oid)));
+
+	owner_id = ((Form_pg_conversion) GETSTRUCT(tuple))->conowner;
+
+	ReleaseSysCache(tuple);
+
+	return userid == owner_id;
+}
diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index 62765a96e0a..2283c563790 100644
--- a/src/backend/commands/comment.c
+++ b/src/backend/commands/comment.c
@@ -7,7 +7,7 @@
  * Copyright (c) 1996-2003, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/comment.c,v 1.73 2003/11/12 21:15:49 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/comment.c,v 1.74 2003/11/21 22:32:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -21,6 +21,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_description.h"
+#include "catalog/pg_largeobject.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_trigger.h"
@@ -58,6 +59,11 @@ 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);
 
 
 /*
@@ -107,6 +113,21 @@ CommentObject(CommentStmt *stmt)
 		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;
 		default:
 			elog(ERROR, "unrecognized object type: %d",
 				 (int) stmt->objtype);
@@ -592,7 +613,10 @@ CommentRule(List *qualname, char *comment)
 							   PointerGetDatum(rulename),
 							   0, 0);
 		if (!HeapTupleIsValid(tuple))
-			elog(ERROR, "cache lookup failed for rule \"%s\"", rulename);
+			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);
@@ -910,3 +934,297 @@ CommentConstraint(List *qualname, char *comment)
 	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;
+	Oid			classoid;
+
+	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));
+
+	/* pg_conversion doesn't have a hard-coded OID, so must look it up */
+	classoid = get_system_catalog_relid(ConversionRelationName);
+
+	/* Call CreateComments() to create/drop the comments */
+	CreateComments(conversionOid, classoid, 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;
+	Oid			classoid;
+	char	   *language;
+
+	if (length(qualname) != 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("language name may not be qualified")));
+	language = strVal(lfirst(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")));
+
+	/* pg_language doesn't have a hard-coded OID, so must look it up */
+	classoid = get_system_catalog_relid(LanguageRelationName);
+
+	/* Call CreateComments() to create/drop the comments */
+	CreateComments(oid, classoid, 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;
+	Oid			classoid;
+	HeapTuple	tuple;
+
+	Assert(length(arguments) == 1);
+	amname = strVal(lfirst(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);
+
+	/* pg_opclass doesn't have a hard-coded OID, so must look it up */
+	classoid = get_system_catalog_relid(OperatorClassRelationName);
+
+	/* Call CreateComments() to create/drop the comments */
+	CreateComments(opcID, classoid, 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;
+	Oid			classoid;
+	Node	   *node; 
+
+	Assert(length(qualname) == 1);
+	node = (Node *) lfirst(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)));
+
+	/* pg_largeobject doesn't have a hard-coded OID, so must look it up */
+	classoid = get_system_catalog_relid(LargeObjectRelationName);
+
+	/* Call CreateComments() to create/drop the comments */
+	CreateComments(loid, classoid, 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;
+	Oid			classoid;
+
+	Assert(length(qualname) == 1);
+	sourcetype = (TypeName *) lfirst(qualname);
+	Assert(IsA(sourcetype, TypeName));
+	Assert(length(arguments) == 1);
+	targettype = (TypeName *) lfirst(arguments);
+	Assert(IsA(targettype, TypeName));
+	
+	sourcetypeid = typenameTypeId(sourcetype);
+	if (!OidIsValid(sourcetypeid))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("source data type %s does not exist",
+						TypeNameToString(sourcetype))));
+
+	targettypeid = typenameTypeId(targettype);
+	if (!OidIsValid(targettypeid))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("target data type %s does not exist",
+						TypeNameToString(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);
+
+	/* pg_cast doesn't have a hard-coded OID, so must look it up */
+	classoid = get_system_catalog_relid(CastRelationName);
+
+	/* Call CreateComments() to create/drop the comments */
+	CreateComments(castOid, classoid, 0, comment);	
+}
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 7e3c7410a10..417220df9d4 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -2,14 +2,15 @@
  *
  * functioncmds.c
  *
- *	  Routines for CREATE and DROP FUNCTION commands
+ *	  Routines for CREATE and DROP FUNCTION commands and CREATE and DROP
+ *        CAST commands.
  *
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/functioncmds.c,v 1.40 2003/11/12 21:15:50 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/functioncmds.c,v 1.41 2003/11/21 22:32:48 tgl Exp $
  *
  * DESCRIPTION
  *	  These routines take the parse tree and pick out the
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5f0b0f4e5da..337eef4480c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.437 2003/11/06 22:08:14 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.438 2003/11/21 22:32:49 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -363,7 +363,7 @@ static void doNegateFloat(Value *v);
 
 	KEY
 
-	LANCOMPILER LANGUAGE LAST_P LEADING LEFT LEVEL LIKE LIMIT
+	LANCOMPILER LANGUAGE LARGE_P LAST_P LEADING LEFT LEVEL LIKE LIMIT
 	LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
 	LOCK_P
 
@@ -373,7 +373,7 @@ static void doNegateFloat(Value *v);
 	NOCREATEUSER NONE NOT NOTHING NOTIFY NOTNULL NULL_P
 	NULLIF NUMERIC
 
-	OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OR
+	OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OR
 	ORDER OUT_P OUTER_P OVERLAPS OVERLAY OWNER
 
 	PARTIAL PASSWORD PATH_P PENDANT PLACING POSITION
@@ -2519,11 +2519,15 @@ TruncateStmt:
  *	The COMMENT ON statement can take different forms based upon the type of
  *	the object associated with the comment. The form of the statement is:
  *
- *	COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | VIEW ]
- *				 <objname> | AGGREGATE <aggname> (<aggtype>) | FUNCTION
- *		 <funcname> (arg1, arg2, ...) | OPERATOR <op>
- *		 (leftoperand_typ rightoperand_typ) | TRIGGER <triggername> ON
- *		 <relname> | RULE <rulename> ON <relname> ] IS 'text'
+ *	COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | VIEW |
+ *				   CONVERSION | LANGUAGE | OPERATOR CLASS | LARGE OBJECT |
+ *				   CAST ] <objname> |
+ *				 AGGREGATE <aggname> (<aggtype>) |
+ *				 FUNCTION <funcname> (arg1, arg2, ...) |
+ *				 OPERATOR <op> (leftoperand_typ, rightoperand_typ) |
+ *				 TRIGGER <triggername> ON <relname> |
+ *				 RULE <rulename> ON <relname> ]
+ *			   IS 'text'
  *
  *****************************************************************************/
 
@@ -2603,6 +2607,42 @@ CommentStmt:
 					n->comment = $8;
 					$$ = (Node *) n;
 				}
+			| COMMENT ON OPERATOR CLASS any_name USING access_method IS comment_text
+				{
+					CommentStmt *n = makeNode(CommentStmt);
+					n->objtype = OBJECT_OPCLASS;
+					n->objname = $5;
+					n->objargs = makeList1(makeString($7));
+					n->comment = $9;
+					$$ = (Node *) n;
+				}
+			| COMMENT ON LARGE_P OBJECT_P NumericOnly IS comment_text
+				{
+					CommentStmt *n = makeNode(CommentStmt);
+					n->objtype = OBJECT_LARGEOBJECT;
+					n->objname = makeList1($5);
+					n->objargs = NIL;
+					n->comment = $7;
+					$$ = (Node *) n;
+				}
+			| COMMENT ON CAST '(' Typename AS Typename ')' IS comment_text
+				{
+					CommentStmt *n = makeNode(CommentStmt);
+					n->objtype = OBJECT_CAST;
+					n->objname = makeList1($5);
+					n->objargs = makeList1($7);
+					n->comment = $10;
+					$$ = (Node *) n;
+				}
+			| COMMENT ON opt_procedural LANGUAGE any_name IS comment_text
+				{
+					CommentStmt *n = makeNode(CommentStmt);
+					n->objtype = OBJECT_LANGUAGE;
+					n->objname = $5;
+					n->objargs = NIL;
+					n->comment = $7;
+					$$ = (Node *) n;
+				}				
 		;
 
 comment_type:
@@ -2615,6 +2655,7 @@ comment_type:
 			| DOMAIN_P							{ $$ = OBJECT_TYPE; }
 			| TYPE_P							{ $$ = OBJECT_TYPE; }
 			| VIEW								{ $$ = OBJECT_VIEW; }
+			| CONVERSION_P						{ $$ = OBJECT_CONVERSION; }
 		;
 
 comment_text:
@@ -7365,6 +7406,7 @@ unreserved_keyword:
 			| KEY
 			| LANCOMPILER
 			| LANGUAGE
+			| LARGE_P
 			| LAST_P
 			| LEVEL
 			| LISTEN
@@ -7387,6 +7429,7 @@ unreserved_keyword:
 			| NOCREATEUSER
 			| NOTHING
 			| NOTIFY
+			| OBJECT_P
 			| OF
 			| OIDS
 			| OPERATOR
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index 6d42a13ef87..c4e50580780 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.142 2003/11/06 22:08:15 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.143 2003/11/21 22:32:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -177,6 +177,7 @@ static const ScanKeyword ScanKeywords[] = {
 	{"key", KEY},
 	{"lancompiler", LANCOMPILER},
 	{"language", LANGUAGE},
+	{"large", LARGE_P},
 	{"last", LAST_P},
 	{"leading", LEADING},
 	{"left", LEFT},
@@ -214,6 +215,7 @@ static const ScanKeyword ScanKeywords[] = {
 	{"null", NULL_P},
 	{"nullif", NULLIF},
 	{"numeric", NUMERIC},
+	{"object", OBJECT_P},
 	{"of", OF},
 	{"off", OFF},
 	{"offset", OFFSET},
diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c
index f777fb33b95..65a4602806d 100644
--- a/src/backend/storage/large_object/inv_api.c
+++ b/src/backend/storage/large_object/inv_api.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/storage/large_object/inv_api.c,v 1.100 2003/11/12 21:15:54 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/storage/large_object/inv_api.c,v 1.101 2003/11/21 22:32:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -31,12 +31,14 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_largeobject.h"
 #include "catalog/pg_type.h"
+#include "commands/comment.h"
 #include "libpq/libpq-fs.h"
 #include "miscadmin.h"
 #include "storage/large_object.h"
 #include "storage/smgr.h"
-#include "utils/fmgroids.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
 
 
 static int32
@@ -174,8 +176,16 @@ inv_close(LargeObjectDesc *obj_desc)
 int
 inv_drop(Oid lobjId)
 {
+	Oid classoid;
+
 	LargeObjectDrop(lobjId);
 
+	/* pg_largeobject doesn't have a hard-coded OID, so must look it up */
+	classoid = get_system_catalog_relid(LargeObjectRelationName);
+
+	/* Delete any comments on the large object */
+	DeleteComments(lobjId, classoid, 0);
+
 	/*
 	 * Advance command counter so that tuple removal will be seen by later
 	 * large-object operations in this transaction.
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 98b43f5cceb..fb4979b453e 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/bin/pg_dump/common.c,v 1.75 2003/08/04 02:40:09 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/bin/pg_dump/common.c,v 1.76 2003/11/21 22:32:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -61,6 +61,7 @@ dumpSchema(Archive *fout,
 	int			numAggregates;
 	int			numOperators;
 	int			numOpclasses;
+	int			numConversions;
 	NamespaceInfo *nsinfo;
 	TypeInfo   *tinfo;
 	FuncInfo   *finfo;
@@ -69,6 +70,7 @@ dumpSchema(Archive *fout,
 	InhInfo    *inhinfo;
 	OprInfo    *oprinfo;
 	OpclassInfo *opcinfo;
+	ConvInfo *convinfo;
 
 	if (g_verbose)
 		write_msg(NULL, "reading schemas\n");
@@ -94,6 +96,10 @@ dumpSchema(Archive *fout,
 		write_msg(NULL, "reading user-defined operator classes\n");
 	opcinfo = getOpclasses(&numOpclasses);
 
+	if (g_verbose)
+		write_msg(NULL, "reading user-defined conversions\n");
+	convinfo = getConversions(&numConversions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading user-defined tables\n");
 	tblinfo = getTables(&numTables);
@@ -190,6 +196,13 @@ dumpSchema(Archive *fout,
 		dumpCasts(fout, finfo, numFuncs, tinfo, numTypes);
 	}
 
+	if (!dataOnly)
+	{
+		if (g_verbose)
+			write_msg(NULL, "dumping out user-defined conversions\n");
+		dumpConversions(fout, convinfo, numConversions);
+	}
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 134e9522a37..f74cd30b5a0 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12,7 +12,7 @@
  *	by PostgreSQL
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_dump.c,v 1.355 2003/10/28 21:05:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_dump.c,v 1.356 2003/11/21 22:32:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -105,6 +105,7 @@ static const char *convertRegProcReference(const char *proc);
 static const char *convertOperatorReference(const char *opr,
 						 OprInfo *g_oprinfo, int numOperators);
 static void dumpOneOpclass(Archive *fout, OpclassInfo *opcinfo);
+static void dumpOneConversion(Archive *fout, ConvInfo *convinfo);
 static void dumpOneAgg(Archive *fout, AggInfo *agginfo);
 static Oid	findLastBuiltinOid_V71(const char *);
 static Oid	findLastBuiltinOid_V70(void);
@@ -1689,6 +1690,79 @@ getOperators(int *numOprs)
 	return oprinfo;
 }
 
+/*
+ * getConversions:
+ *	  read all conversions in the system catalogs and return them in the
+ * ConvInfo* structure
+ *
+ *	numConversions is set to the number of conversions read in
+ */
+ConvInfo *
+getConversions(int *numConversions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	ConvInfo *convinfo;
+	int			i_oid;
+	int			i_conname;
+	int			i_connamespace;
+	int			i_usename;
+
+	/* Conversions didn't exist pre-7.3 */
+	if (g_fout->remoteVersion < 70300) {
+		*numConversions = 0;
+		return NULL;
+	}
+
+	/*
+	 * find all conversions, including builtin conversions; we filter out
+	 * system-defined conversions at dump-out time.
+	 */
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema("pg_catalog");
+
+	appendPQExpBuffer(query, "SELECT pg_conversion.oid, conname, "
+					  "connamespace, "
+					  "(select usename from pg_user where conowner = usesysid) as usename "
+					  "from pg_conversion");
+
+	res = PQexec(g_conn, query->data);
+	if (!res ||
+		PQresultStatus(res) != PGRES_TUPLES_OK)
+	{
+		write_msg(NULL, "query to obtain list of conversions failed: %s", PQerrorMessage(g_conn));
+		exit_nicely();
+	}
+
+	ntups = PQntuples(res);
+	*numConversions = ntups;
+
+	convinfo = (ConvInfo *) malloc(ntups * sizeof(ConvInfo));
+
+	i_oid = PQfnumber(res, "oid");
+	i_conname = PQfnumber(res, "conname");
+	i_connamespace = PQfnumber(res, "connamespace");
+	i_usename = PQfnumber(res, "usename");
+
+	for (i = 0; i < ntups; i++)
+	{
+		convinfo[i].oid = strdup(PQgetvalue(res, i, i_oid));
+		convinfo[i].conname = strdup(PQgetvalue(res, i, i_conname));
+		convinfo[i].connamespace = findNamespace(PQgetvalue(res, i, i_connamespace),
+												convinfo[i].oid);
+		convinfo[i].usename = strdup(PQgetvalue(res, i, i_usename));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return convinfo;
+}
+
 /*
  * getOpclasses:
  *	  read all opclasses in the system catalogs and return them in the
@@ -3414,6 +3488,7 @@ dumpOneCompositeType(Archive *fout, TypeInfo *tinfo)
 				 tinfo->usename, "TYPE", NULL,
 				 q->data, delq->data, NULL, NULL, NULL);
 
+
 	/* Dump Type Comments */
 	resetPQExpBuffer(q);
 
@@ -3614,6 +3689,14 @@ dumpProcLangs(Archive *fout, FuncInfo finfo[], int numFuncs)
 					NULL, lanacl, lanoid);
 			free(tmp);
 		}
+
+		/* Dump Proc Lang Comments */
+		resetPQExpBuffer(defqry);
+
+		appendPQExpBuffer(defqry, "LANGUAGE %s", fmtId(lanname));
+		dumpComment(fout, defqry->data,
+					NULL, "",
+					lanoid, "pg_language", 0, NULL);
 	}
 
 	PQclear(res);
@@ -4019,6 +4102,16 @@ dumpCasts(Archive *fout,
 					 "CAST", deps,
 					 defqry->data, delqry->data,
 					 NULL, NULL, NULL);
+
+		/* Dump Cast Comments */
+		resetPQExpBuffer(defqry);
+		appendPQExpBuffer(defqry, "CAST (%s AS %s)",
+						  getFormattedTypeName(castsource, zeroAsNone),
+						  getFormattedTypeName(casttarget, zeroAsNone));
+		dumpComment(fout, defqry->data,
+					NULL, "",
+					castoid, "pg_cast", 0, NULL);
+
 	}
 
 	PQclear(res);
@@ -4490,7 +4583,8 @@ dumpOneOpclass(Archive *fout, OpclassInfo *opcinfo)
 	opcintype = PQgetvalue(res, 0, i_opcintype);
 	opckeytype = PQgetvalue(res, 0, i_opckeytype);
 	opcdefault = PQgetvalue(res, 0, i_opcdefault);
-	amname = PQgetvalue(res, 0, i_amname);
+	/* amname will still be needed after we PQclear res */
+	amname = strdup(PQgetvalue(res, 0, i_amname));
 
 	/*
 	 * DROP must be fully qualified in case same name appears in
@@ -4617,11 +4711,145 @@ dumpOneOpclass(Archive *fout, OpclassInfo *opcinfo)
 				 q->data, delq->data,
 				 NULL, NULL, NULL);
 
+	/* Dump Operator Class Comments */
+	resetPQExpBuffer(q);
+	appendPQExpBuffer(q, "OPERATOR CLASS %s",
+					  fmtId(opcinfo->opcname));
+	appendPQExpBuffer(q, " USING %s",
+					  fmtId(amname));
+	dumpComment(fout, q->data,
+				NULL, opcinfo->usename,
+				opcinfo->oid, "pg_opclass", 0, NULL);
+
+	free(amname);
 	destroyPQExpBuffer(query);
 	destroyPQExpBuffer(q);
 	destroyPQExpBuffer(delq);
 }
 
+/*
+ * dumpConversions
+ *	  writes out to fout the queries to create all the user-defined conversions
+ */
+void
+dumpConversions(Archive *fout, ConvInfo convinfo[], int numConvs)
+{
+	int			i;
+
+	for (i = 0; i < numConvs; i++)
+	{
+		/* Dump only conversions in dumpable namespaces */
+		if (!convinfo[i].connamespace->dump)
+			continue;
+
+		dumpOneConversion(fout, &convinfo[i]);
+	}
+}
+
+/*
+ * dumpOneConversion
+ *	  write out a single conversion definition
+ */
+static void
+dumpOneConversion(Archive *fout, ConvInfo *convinfo)
+{
+	PQExpBuffer query = createPQExpBuffer();
+	PQExpBuffer q = createPQExpBuffer();
+	PQExpBuffer delq = createPQExpBuffer();
+	PQExpBuffer details = createPQExpBuffer();
+	PGresult   *res;
+	int			ntups;
+	int			i_conname;
+	int			i_conforencoding;
+	int			i_contoencoding;
+	int			i_conproc;
+	int			i_condefault;
+	const char *conname;
+	const char *conforencoding;
+	const char *contoencoding;
+	const char *conproc;
+	bool		condefault;
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(convinfo->connamespace->nspname);
+
+	/* Get conversion-specific details */
+	appendPQExpBuffer(query, "SELECT conname,
+					pg_catalog.pg_encoding_to_char(conforencoding) AS conforencoding,
+					pg_catalog.pg_encoding_to_char(contoencoding) AS contoencoding,
+					conproc, condefault
+				FROM pg_catalog.pg_conversion c
+				WHERE c.oid = '%s'::pg_catalog.oid",
+					convinfo->oid);
+
+	res = PQexec(g_conn, query->data);
+	if (!res ||
+		PQresultStatus(res) != PGRES_TUPLES_OK)
+	{
+		write_msg(NULL, "query to obtain conversion failed: %s",
+				  PQerrorMessage(g_conn));
+		exit_nicely();
+	}
+
+	/* Expecting a single result only */
+	ntups = PQntuples(res);
+	if (ntups != 1)
+	{
+		write_msg(NULL, "Got %d rows instead of one from: %s",
+				  ntups, query->data);
+		exit_nicely();
+	}
+
+	i_conname = PQfnumber(res, "conname");
+	i_conforencoding = PQfnumber(res, "conforencoding");
+	i_contoencoding = PQfnumber(res, "contoencoding");
+	i_conproc = PQfnumber(res, "conproc");
+	i_condefault = PQfnumber(res, "condefault");
+
+	conname = PQgetvalue(res, 0, i_conname);
+	conforencoding = PQgetvalue(res, 0, i_conforencoding);
+	contoencoding = PQgetvalue(res, 0, i_contoencoding);
+	conproc = PQgetvalue(res, 0, i_conproc);
+	condefault = (PQgetvalue(res, 0, i_condefault)[0] == 't');
+
+	/*
+	 * DROP must be fully qualified in case same name appears in
+	 * pg_catalog
+	 */
+	appendPQExpBuffer(delq, "DROP CONVERSION %s",
+					  fmtId(convinfo->connamespace->nspname));
+	appendPQExpBuffer(delq, ".%s;\n",
+					  fmtId(convinfo->conname));
+
+	appendPQExpBuffer(q, "CREATE %sCONVERSION %s FOR ",
+					(condefault) ? "DEFAULT " : "",	
+					fmtId(convinfo->conname));
+	appendStringLiteral(q, conforencoding, true);
+	appendPQExpBuffer(q, " TO ");
+	appendStringLiteral(q, contoencoding, true);
+	/* regproc is automatically quoted in 7.3 and above */
+	appendPQExpBuffer(q, " FROM %s;\n", conproc);
+	
+	ArchiveEntry(fout, convinfo->oid, convinfo->conname,
+				 convinfo->connamespace->nspname, convinfo->usename,
+				 "CONVERSION", NULL,
+				 q->data, delq->data,
+				 NULL, NULL, NULL);
+
+	/* Dump Conversion Comments */
+	resetPQExpBuffer(q);
+	appendPQExpBuffer(q, "CONVERSION %s", fmtId(convinfo->conname));
+	dumpComment(fout, q->data,
+				convinfo->connamespace->nspname, convinfo->usename,
+				convinfo->oid, "pg_conversion", 0, NULL);
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+	destroyPQExpBuffer(q);
+	destroyPQExpBuffer(delq);
+	destroyPQExpBuffer(details);
+}
 
 /*
  * dumpAggs
@@ -6573,8 +6801,10 @@ dumpRules(Archive *fout, TableInfo *tblinfo, int numTables)
 			/* Dump rule comments */
 
 			resetPQExpBuffer(query);
-			appendPQExpBuffer(query, "RULE %s", fmtId(PQgetvalue(res, i, i_rulename)));
-			appendPQExpBuffer(query, " ON %s", fmtId(tbinfo->relname));
+			appendPQExpBuffer(query, "RULE %s",
+							  fmtId(PQgetvalue(res, i, i_rulename)));
+			appendPQExpBuffer(query, " ON %s",
+							  fmtId(tbinfo->relname));
 			dumpComment(fout, query->data,
 						tbinfo->relnamespace->nspname,
 						tbinfo->usename,
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index a68a99ade07..1e6e2fbe112 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_dump.h,v 1.104 2003/08/08 04:52:21 momjian Exp $
+ * $Id: pg_dump.h,v 1.105 2003/11/21 22:32:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -95,6 +95,14 @@ typedef struct _opclassInfo
 	char	   *usename;
 } OpclassInfo;
 
+typedef struct _convInfo
+{
+	char	   *oid;
+	char	   *conname;
+	NamespaceInfo *connamespace;	/* link to containing namespace */
+	char	   *usename;
+} ConvInfo;
+
 typedef struct _tableInfo
 {
 	/*
@@ -213,6 +221,7 @@ extern FuncInfo *getFuncs(int *numFuncs);
 extern AggInfo *getAggregates(int *numAggregates);
 extern OprInfo *getOperators(int *numOperators);
 extern OpclassInfo *getOpclasses(int *numOpclasses);
+extern ConvInfo *getConversions(int *numConversions);
 extern TableInfo *getTables(int *numTables);
 extern InhInfo *getInherits(int *numInherits);
 
@@ -230,6 +239,8 @@ extern void dumpAggs(Archive *fout, AggInfo agginfo[], int numAggregates);
 extern void dumpOprs(Archive *fout, OprInfo *oprinfo, int numOperators);
 extern void dumpOpclasses(Archive *fout,
 			  OpclassInfo *opcinfo, int numOpclasses);
+extern void dumpConversions(Archive *fout,
+			  ConvInfo *coninfo, int numConversions);
 extern void dumpTables(Archive *fout, TableInfo tblinfo[], int numTables,
 		   const bool aclsSkip,
 		   const bool schemaOnly, const bool dataOnly);
diff --git a/src/bin/psql/large_obj.c b/src/bin/psql/large_obj.c
index 53235b423ae..16cb1b947a7 100644
--- a/src/bin/psql/large_obj.c
+++ b/src/bin/psql/large_obj.c
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2003, PostgreSQL Global Development Group
  *
- * $Header: /cvsroot/pgsql/src/bin/psql/large_obj.c,v 1.29 2003/08/04 23:59:40 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/bin/psql/large_obj.c,v 1.30 2003/11/21 22:32:49 tgl Exp $
  */
 #include "postgres_fe.h"
 #include "large_obj.h"
@@ -165,9 +165,7 @@ do_lo_import(const char *filename_arg, const char *comment_arg)
 	}
 
 	/* insert description if given */
-	/* XXX don't try to hack pg_description if not superuser */
-	/* XXX ought to replace this with some kind of COMMENT command */
-	if (comment_arg && is_superuser())
+	if (comment_arg)
 	{
 		char	   *cmdbuf;
 		char	   *bufptr;
@@ -177,9 +175,7 @@ do_lo_import(const char *filename_arg, const char *comment_arg)
 		if (!cmdbuf)
 			return fail_lo_xact("\\lo_import", own_transaction);
 		sprintf(cmdbuf,
-				"INSERT INTO pg_catalog.pg_description VALUES ('%u', "
-				"'pg_catalog.pg_largeobject'::regclass, "
-				"0, '",
+				"COMMENT ON LARGE OBJECT %u IS '",
 				loid);
 		bufptr = cmdbuf + strlen(cmdbuf);
 		for (i = 0; i < slen; i++)
@@ -188,7 +184,7 @@ do_lo_import(const char *filename_arg, const char *comment_arg)
 				*bufptr++ = '\\';
 			*bufptr++ = comment_arg[i];
 		}
-		strcpy(bufptr, "')");
+		strcpy(bufptr, "'");
 
 		if (!(res = PSQLexec(cmdbuf, false)))
 		{
@@ -219,10 +215,8 @@ do_lo_import(const char *filename_arg, const char *comment_arg)
 bool
 do_lo_unlink(const char *loid_arg)
 {
-	PGresult   *res;
 	int			status;
 	Oid			loid = atooid(loid_arg);
-	char		buf[256];
 	bool		own_transaction;
 
 	if (!start_lo_xact("\\lo_unlink", &own_transaction))
@@ -235,20 +229,6 @@ do_lo_unlink(const char *loid_arg)
 		return fail_lo_xact("\\lo_unlink", own_transaction);
 	}
 
-	/* remove the comment as well */
-	/* XXX don't try to hack pg_description if not superuser */
-	/* XXX ought to replace this with some kind of COMMENT command */
-	if (is_superuser())
-	{
-		snprintf(buf, sizeof(buf),
-			 "DELETE FROM pg_catalog.pg_description WHERE objoid = '%u' "
-				 "AND classoid = 'pg_catalog.pg_largeobject'::regclass",
-				 loid);
-		if (!(res = PSQLexec(buf, false)))
-			return fail_lo_xact("\\lo_unlink", own_transaction);
-		PQclear(res);
-	}
-
 	if (!finish_lo_xact("\\lo_unlink", own_transaction))
 		return false;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2aec3b7b7ac..e0fada5dbef 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsenodes.h,v 1.248 2003/09/17 04:25:29 ishii Exp $
+ * $Id: parsenodes.h,v 1.249 2003/11/21 22:32:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -686,6 +686,7 @@ typedef enum ObjectType
 	OBJECT_GROUP,
 	OBJECT_INDEX,
 	OBJECT_LANGUAGE,
+	OBJECT_LARGEOBJECT,
 	OBJECT_OPCLASS,
 	OBJECT_OPERATOR,
 	OBJECT_RULE,
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 8a4c235cea4..53fb98df934 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: acl.h,v 1.63 2003/10/29 22:20:54 tgl Exp $
+ * $Id: acl.h,v 1.64 2003/11/21 22:32:49 tgl Exp $
  *
  * NOTES
  *	  An ACL array is simply an array of AclItems, representing the union
@@ -245,5 +245,6 @@ extern bool pg_proc_ownercheck(Oid proc_oid, AclId userid);
 extern bool pg_namespace_ownercheck(Oid nsp_oid, AclId userid);
 extern bool pg_opclass_ownercheck(Oid opc_oid, AclId userid);
 extern bool pg_database_ownercheck(Oid db_oid, AclId userid);
+extern bool pg_conversion_ownercheck(Oid conv_oid, AclId userid);
 
 #endif   /* ACL_H */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 61a3965fe59..cc50cb86dae 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3,6 +3,10 @@
 -- add attribute
 --
 CREATE TABLE tmp (initial int4);
+COMMENT ON TABLE tmp_wrong IS 'table comment';
+ERROR:  relation "tmp_wrong" does not exist
+COMMENT ON TABLE tmp IS 'table comment';
+COMMENT ON TABLE tmp IS NULL;
 ALTER TABLE tmp ADD COLUMN a int4;
 ALTER TABLE tmp ADD COLUMN b name;
 ALTER TABLE tmp ADD COLUMN c text;
diff --git a/src/test/regress/expected/conversion.out b/src/test/regress/expected/conversion.out
index 289bd0a27f1..c0e85b85a1d 100644
--- a/src/test/regress/expected/conversion.out
+++ b/src/test/regress/expected/conversion.out
@@ -18,6 +18,11 @@ CREATE DEFAULT CONVERSION public.mydef FOR 'LATIN1' TO 'UNICODE' FROM iso8859_1_
 --
 CREATE DEFAULT CONVERSION public.mydef2 FOR 'LATIN1' TO 'UNICODE' FROM iso8859_1_to_utf8;
 ERROR:  default conversion for LATIN1 to UNICODE already exists
+-- test comments
+COMMENT ON CONVERSION myconv_bad IS 'foo';
+ERROR:  conversion "myconv_bad" does not exist
+COMMENT ON CONVERSION myconv IS 'bar';
+COMMENT ON CONVERSION myconv IS NULL;
 --
 -- drop user defined conversion
 --
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
index ef83c886a11..b0fec460cbb 100644
--- a/src/test/regress/expected/create_aggregate.out
+++ b/src/test/regress/expected/create_aggregate.out
@@ -7,6 +7,11 @@ CREATE AGGREGATE newavg (
    finalfunc = numeric_avg,
    initcond1 = '{0,0,0}'
 );
+-- test comments
+COMMENT ON AGGREGATE newavg_wrong (int4) IS 'an agg comment';
+ERROR:  aggregate newavg_wrong(integer) does not exist
+COMMENT ON AGGREGATE newavg (int4) IS 'an agg comment';
+COMMENT ON AGGREGATE newavg (int4) IS NULL;
 -- without finalfunc; test obsolete spellings 'sfunc1' etc
 CREATE AGGREGATE newsum (
    sfunc1 = int4pl, basetype = int4, stype1 = int4, 
@@ -17,3 +22,7 @@ CREATE AGGREGATE newcnt (
    sfunc = int4inc, basetype = 'any', stype = int4,
    initcond = '0'
 );
+COMMENT ON AGGREGATE nosuchagg (*) IS 'should fail';
+ERROR:  aggregate nosuchagg(*) does not exist
+COMMENT ON AGGREGATE newcnt (*) IS 'an any agg comment';
+COMMENT ON AGGREGATE newcnt (*) IS NULL;
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 46d2657d015..6643070e717 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -18,6 +18,11 @@ CREATE INDEX tenk2_hundred ON tenk2 USING btree(hundred int4_ops);
 CREATE INDEX rix ON road USING btree (name text_ops);
 CREATE INDEX iix ON ihighway USING btree (name text_ops);
 CREATE INDEX six ON shighway USING btree (name text_ops);
+-- test comments
+COMMENT ON INDEX six_wrong IS 'bad index';
+ERROR:  relation "six_wrong" does not exist
+COMMENT ON INDEX six IS 'good index';
+COMMENT ON INDEX six IS NULL;
 --
 -- BTREE ascending/descending cases
 --
diff --git a/src/test/regress/expected/create_operator.out b/src/test/regress/expected/create_operator.out
index 2338b7c7108..7685dbe802c 100644
--- a/src/test/regress/expected/create_operator.out
+++ b/src/test/regress/expected/create_operator.out
@@ -26,3 +26,8 @@ CREATE OPERATOR #%# (
    leftarg = int4,		-- right unary 
    procedure = int4fac 
 );
+-- Test comments
+COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad right unary';
+ERROR:  operator does not exist: integer ######
+COMMENT ON OPERATOR #%# (int4, NONE) IS 'right unary';
+COMMENT ON OPERATOR #%# (int4, NONE) IS NULL;
diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out
index 210da92cf2d..101382e7392 100644
--- a/src/test/regress/expected/create_type.out
+++ b/src/test/regress/expected/create_type.out
@@ -71,6 +71,11 @@ SELECT * FROM get_default_test();
  zippo | 42
 (1 row)
 
+-- Test comments
+COMMENT ON TYPE bad IS 'bad comment';
+ERROR:  type "bad" does not exist
+COMMENT ON TYPE default_test_row IS 'good comment';
+COMMENT ON TYPE default_test_row IS NULL;
 DROP TYPE default_test_row CASCADE;
 NOTICE:  drop cascades to function get_default_test()
 DROP TABLE default_test;
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index 630855ed37c..04e62f70846 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -15,6 +15,11 @@ CREATE VIEW iexit AS
 CREATE VIEW toyemp AS
    SELECT name, age, location, 12*salary AS annualsal
    FROM emp;
+-- Test comments
+COMMENT ON VIEW noview IS 'no view';
+ERROR:  relation "noview" does not exist
+COMMENT ON VIEW toyemp IS 'is a view';
+COMMENT ON VIEW toyemp IS NULL;
 --
 -- CREATE OR REPLACE VIEW
 --
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index bfb90979f68..57a4ae16471 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -64,6 +64,11 @@ CREATE TABLE PKTABLE ( ptest1 int, ptest2 int, ptest3 text, PRIMARY KEY(ptest1,
 NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" for table "pktable"
 CREATE TABLE FKTABLE ( ftest1 int, ftest2 int, ftest3 int, CONSTRAINT constrname FOREIGN KEY(ftest1, ftest2) 
                        REFERENCES PKTABLE MATCH FULL ON DELETE SET NULL ON UPDATE SET NULL);
+-- Test comments
+COMMENT ON CONSTRAINT constrname_wrong ON FKTABLE IS 'fk constraint comment';
+ERROR:  constraint "constrname_wrong" for table "fktable" does not exist
+COMMENT ON CONSTRAINT constrname ON FKTABLE IS 'fk constraint comment';
+COMMENT ON CONSTRAINT constrname ON FKTABLE IS NULL;
 -- Insert test data into PKTABLE
 INSERT INTO PKTABLE VALUES (1, 2, 'Test1');
 INSERT INTO PKTABLE VALUES (1, 3, 'Test1-2');
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index a7a380b5c6c..694165e506a 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -264,6 +264,11 @@ begin
     return 0;
 end;
 ' language 'plpgsql';
+-- Test comments
+COMMENT ON FUNCTION tg_hub_adjustslots_wrong(bpchar, integer, integer) IS 'function with args';
+ERROR:  function tg_hub_adjustslots_wrong(character, integer, integer) does not exist
+COMMENT ON FUNCTION tg_hub_adjustslots(bpchar, integer, integer) IS 'function with args';
+COMMENT ON FUNCTION tg_hub_adjustslots(bpchar, integer, integer) IS NULL;
 -- ************************************************************
 -- * BEFORE INSERT or UPDATE on HSlot
 -- *	- prevent from manual manipulation
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 7ae49e8b442..ae7d0feea63 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -17,6 +17,11 @@ create rule rtest_v1_upd as on update to rtest_v1 do instead
 	where a = old.a;
 create rule rtest_v1_del as on delete to rtest_v1 do instead
 	delete from rtest_t1 where a = old.a;
+-- Test comments
+COMMENT ON RULE rtest_v1_bad ON rtest_v1 IS 'bad rule';
+ERROR:  rule "rtest_v1_bad" for relation "rtest_v1" does not exist
+COMMENT ON RULE rtest_v1_del ON rtest_v1 IS 'delete rule';
+COMMENT ON RULE rtest_v1_del ON rtest_v1 IS NULL;
 --
 -- Tables and rules for the constraint update/delete test
 --
diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out
index 49b783a805a..d09e5db1701 100644
--- a/src/test/regress/expected/sequence.out
+++ b/src/test/regress/expected/sequence.out
@@ -71,3 +71,8 @@ SELECT nextval('sequence_test2');
        5
 (1 row)
 
+-- Test comments
+COMMENT ON SEQUENCE asdf IS 'won''t work';
+ERROR:  relation "asdf" does not exist
+COMMENT ON SEQUENCE sequence_test2 IS 'will work';
+COMMENT ON SEQUENCE sequence_test2 IS NULL;
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 086c9f602c8..c44a81a75f8 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -37,6 +37,11 @@ create trigger check_fkeys2_pkey_exist
 	for each row 
 	execute procedure 
 	check_primary_key ('fkey21', 'fkey22', 'pkeys', 'pkey1', 'pkey2');
+-- Test comments
+COMMENT ON TRIGGER check_fkeys2_pkey_bad ON fkeys2 IS 'wrong';
+ERROR:  trigger "check_fkeys2_pkey_bad" for table "fkeys2" does not exist
+COMMENT ON TRIGGER check_fkeys2_pkey_exist ON fkeys2 IS 'right';
+COMMENT ON TRIGGER check_fkeys2_pkey_exist ON fkeys2 IS NULL;
 --
 -- For pkeys:
 -- 	ON DELETE/UPDATE (pkey1, pkey2) CASCADE:
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 9bdc8cd1eaf..33187c83526 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -5,6 +5,10 @@
 
 CREATE TABLE tmp (initial int4);
 
+COMMENT ON TABLE tmp_wrong IS 'table comment';
+COMMENT ON TABLE tmp IS 'table comment';
+COMMENT ON TABLE tmp IS NULL;
+
 ALTER TABLE tmp ADD COLUMN a int4;
 
 ALTER TABLE tmp ADD COLUMN b name;
diff --git a/src/test/regress/sql/conversion.sql b/src/test/regress/sql/conversion.sql
index 979a7cc0fdd..c84ffee95fe 100644
--- a/src/test/regress/sql/conversion.sql
+++ b/src/test/regress/sql/conversion.sql
@@ -16,6 +16,10 @@ CREATE DEFAULT CONVERSION public.mydef FOR 'LATIN1' TO 'UNICODE' FROM iso8859_1_
 -- cannot make default conversion with same shcema/for_encoding/to_encoding
 --
 CREATE DEFAULT CONVERSION public.mydef2 FOR 'LATIN1' TO 'UNICODE' FROM iso8859_1_to_utf8;
+-- test comments
+COMMENT ON CONVERSION myconv_bad IS 'foo';
+COMMENT ON CONVERSION myconv IS 'bar';
+COMMENT ON CONVERSION myconv IS NULL;
 --
 -- drop user defined conversion
 --
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index 5d42ed057e3..4188760c87c 100644
--- a/src/test/regress/sql/create_aggregate.sql
+++ b/src/test/regress/sql/create_aggregate.sql
@@ -9,6 +9,11 @@ CREATE AGGREGATE newavg (
    initcond1 = '{0,0,0}'
 );
 
+-- test comments
+COMMENT ON AGGREGATE newavg_wrong (int4) IS 'an agg comment';
+COMMENT ON AGGREGATE newavg (int4) IS 'an agg comment';
+COMMENT ON AGGREGATE newavg (int4) IS NULL;
+
 -- without finalfunc; test obsolete spellings 'sfunc1' etc
 CREATE AGGREGATE newsum (
    sfunc1 = int4pl, basetype = int4, stype1 = int4, 
@@ -21,3 +26,6 @@ CREATE AGGREGATE newcnt (
    initcond = '0'
 );
 
+COMMENT ON AGGREGATE nosuchagg (*) IS 'should fail';
+COMMENT ON AGGREGATE newcnt (*) IS 'an any agg comment';
+COMMENT ON AGGREGATE newcnt (*) IS NULL;
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index d904bacd66b..6fa0b91e83c 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -32,6 +32,11 @@ CREATE INDEX iix ON ihighway USING btree (name text_ops);
 
 CREATE INDEX six ON shighway USING btree (name text_ops);
 
+-- test comments
+COMMENT ON INDEX six_wrong IS 'bad index';
+COMMENT ON INDEX six IS 'good index';
+COMMENT ON INDEX six IS NULL;
+
 --
 -- BTREE ascending/descending cases
 --
diff --git a/src/test/regress/sql/create_operator.sql b/src/test/regress/sql/create_operator.sql
index c4251144ca3..4167bf3ab82 100644
--- a/src/test/regress/sql/create_operator.sql
+++ b/src/test/regress/sql/create_operator.sql
@@ -32,3 +32,9 @@ CREATE OPERATOR #%# (
    procedure = int4fac 
 );
 
+-- Test comments
+COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad right unary';
+COMMENT ON OPERATOR #%# (int4, NONE) IS 'right unary';
+COMMENT ON OPERATOR #%# (int4, NONE) IS NULL;
+
+
diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql
index ddcba72624e..6e8fa84b7dc 100644
--- a/src/test/regress/sql/create_type.sql
+++ b/src/test/regress/sql/create_type.sql
@@ -69,6 +69,11 @@ CREATE FUNCTION get_default_test() RETURNS SETOF default_test_row AS '
 
 SELECT * FROM get_default_test();
 
+-- Test comments
+COMMENT ON TYPE bad IS 'bad comment';
+COMMENT ON TYPE default_test_row IS 'good comment';
+COMMENT ON TYPE default_test_row IS NULL;
+
 DROP TYPE default_test_row CASCADE;
 
 DROP TABLE default_test;
diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql
index 8c15fc12417..acc82b3e0e0 100644
--- a/src/test/regress/sql/create_view.sql
+++ b/src/test/regress/sql/create_view.sql
@@ -19,6 +19,11 @@ CREATE VIEW toyemp AS
    SELECT name, age, location, 12*salary AS annualsal
    FROM emp;
 
+-- Test comments
+COMMENT ON VIEW noview IS 'no view';
+COMMENT ON VIEW toyemp IS 'is a view';
+COMMENT ON VIEW toyemp IS NULL;
+
 --
 -- CREATE OR REPLACE VIEW
 --
diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql
index 009588db065..10e4da0baf0 100644
--- a/src/test/regress/sql/foreign_key.sql
+++ b/src/test/regress/sql/foreign_key.sql
@@ -50,6 +50,11 @@ CREATE TABLE PKTABLE ( ptest1 int, ptest2 int, ptest3 text, PRIMARY KEY(ptest1,
 CREATE TABLE FKTABLE ( ftest1 int, ftest2 int, ftest3 int, CONSTRAINT constrname FOREIGN KEY(ftest1, ftest2) 
                        REFERENCES PKTABLE MATCH FULL ON DELETE SET NULL ON UPDATE SET NULL);
 
+-- Test comments
+COMMENT ON CONSTRAINT constrname_wrong ON FKTABLE IS 'fk constraint comment';
+COMMENT ON CONSTRAINT constrname ON FKTABLE IS 'fk constraint comment';
+COMMENT ON CONSTRAINT constrname ON FKTABLE IS NULL;
+
 -- Insert test data into PKTABLE
 INSERT INTO PKTABLE VALUES (1, 2, 'Test1');
 INSERT INTO PKTABLE VALUES (1, 3, 'Test1-2');
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index b4d0186458d..9cb7f7c9407 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -326,6 +326,10 @@ begin
 end;
 ' language 'plpgsql';
 
+-- Test comments
+COMMENT ON FUNCTION tg_hub_adjustslots_wrong(bpchar, integer, integer) IS 'function with args';
+COMMENT ON FUNCTION tg_hub_adjustslots(bpchar, integer, integer) IS 'function with args';
+COMMENT ON FUNCTION tg_hub_adjustslots(bpchar, integer, integer) IS NULL;
 
 -- ************************************************************
 -- * BEFORE INSERT or UPDATE on HSlot
@@ -1603,4 +1607,4 @@ END;' language 'plpgsql';
 SELECT perform_test_func();
 SELECT * FROM perform_test;
 
-drop table perform_test;
\ No newline at end of file
+drop table perform_test;
diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql
index d797144d9d1..55c3805bd5f 100644
--- a/src/test/regress/sql/rules.sql
+++ b/src/test/regress/sql/rules.sql
@@ -19,7 +19,10 @@ create rule rtest_v1_upd as on update to rtest_v1 do instead
 	where a = old.a;
 create rule rtest_v1_del as on delete to rtest_v1 do instead
 	delete from rtest_t1 where a = old.a;
-
+-- Test comments
+COMMENT ON RULE rtest_v1_bad ON rtest_v1 IS 'bad rule';
+COMMENT ON RULE rtest_v1_del ON rtest_v1 IS 'delete rule';
+COMMENT ON RULE rtest_v1_del ON rtest_v1 IS NULL;
 --
 -- Tables and rules for the constraint update/delete test
 --
diff --git a/src/test/regress/sql/sequence.sql b/src/test/regress/sql/sequence.sql
index 6f3c1f22ddb..07f5765faf2 100644
--- a/src/test/regress/sql/sequence.sql
+++ b/src/test/regress/sql/sequence.sql
@@ -37,3 +37,8 @@ SELECT nextval('sequence_test2');
 SELECT nextval('sequence_test2');
 SELECT nextval('sequence_test2');
 
+-- Test comments
+COMMENT ON SEQUENCE asdf IS 'won''t work';
+COMMENT ON SEQUENCE sequence_test2 IS 'will work';
+COMMENT ON SEQUENCE sequence_test2 IS NULL;
+
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 37bfb3bfd57..f766e625545 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -44,6 +44,11 @@ create trigger check_fkeys2_pkey_exist
 	execute procedure 
 	check_primary_key ('fkey21', 'fkey22', 'pkeys', 'pkey1', 'pkey2');
 
+-- Test comments
+COMMENT ON TRIGGER check_fkeys2_pkey_bad ON fkeys2 IS 'wrong';
+COMMENT ON TRIGGER check_fkeys2_pkey_exist ON fkeys2 IS 'right';
+COMMENT ON TRIGGER check_fkeys2_pkey_exist ON fkeys2 IS NULL;
+
 --
 -- For pkeys:
 -- 	ON DELETE/UPDATE (pkey1, pkey2) CASCADE:
-- 
GitLab