diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 51df17248a5ebad7d7fb144a396753bfdcad3d3e..490d7106435510f3c87771d53c62ee9bbfd54719 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13930,6 +13930,10 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
     <primary>pg_describe_object</primary>
    </indexterm>
 
+   <indexterm>
+    <primary>pg_identify_object</primary>
+   </indexterm>
+
    <indexterm>
     <primary>pg_get_constraintdef</primary>
    </indexterm>
@@ -14029,6 +14033,11 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
        <entry><type>text</type></entry>
        <entry>get description of a database object</entry>
       </row>
+      <row>
+       <entry><literal><function>pg_identify_object(<parameter>catalog_id</parameter> <type>oid</>, <parameter>object_id</parameter> <type>oid</>, <parameter>object_sub_id</parameter> <type>integer</>)</function></literal></entry>
+       <entry><parameter>type</> <type>text</>, <parameter>schema</> <type>text</>, <parameter>name</> <type>text</>, <parameter>identity</> <type>text</></entry>
+       <entry>get identity of a database object</entry>
+      </row>
       <row>
        <entry><literal><function>pg_get_constraintdef(<parameter>constraint_oid</parameter>)</function></literal></entry>
        <entry><type>text</type></entry>
@@ -14273,12 +14282,30 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
   </para>
 
   <para>
-   <function>pg_describe_object</function> returns a description of a database
+   <function>pg_describe_object</function> returns a textual description of a database
    object specified by catalog OID, object OID and a (possibly zero) sub-object ID.
+   This description is intended to be human-readable, and might be translated,
+   depending on server configuration.
    This is useful to determine the identity of an object as stored in the
    <structname>pg_depend</structname> catalog.
   </para>
 
+  <para>
+   <function>pg_identify_object</function> returns a row containing enough information
+   to uniquely identify the database object specified by catalog OID, object OID and a
+   (possibly zero) sub-object ID.  This information is intended to be machine-readable,
+   and is never translated.
+   <parameter>type</> identifies the type of database object;
+   <parameter>schema</> is the schema name that the object belongs in, or
+   <literal>NULL</> for object types that do not belong to schemas;
+   <parameter>name</> is the name of the object, quoted if necessary, only
+   present if it can be used (alongside schema name, if pertinent) as an unique
+   identifier of the object, otherwise <literal>NULL</>;
+   <parameter>identity</> is the complete object identity, with the precise format
+   depending on object type, and each part within the format being
+   schema-qualified and quoted as necessary.
+  </para>
+
   <para>
    <function>pg_typeof</function> returns the OID of the data type of the
    value that is passed to it.  This can be helpful for troubleshooting or
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 7b8e0246339d4053074d34edb13ea250f1a8d5e1..ddf199049ef48032beff595b57dc7ba98c072a12 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -15,12 +15,10 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
-#include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
 #include "catalog/index.h"
-#include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
@@ -56,24 +54,18 @@
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
 #include "commands/comment.h"
-#include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
 #include "commands/extension.h"
 #include "commands/proclang.h"
 #include "commands/schemacmds.h"
 #include "commands/seclabel.h"
-#include "commands/tablespace.h"
 #include "commands/trigger.h"
 #include "commands/typecmds.h"
-#include "foreign/foreign.h"
-#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteRemove.h"
 #include "storage/lmgr.h"
-#include "utils/acl.h"
-#include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
@@ -196,8 +188,6 @@ static bool object_address_present_add_flags(const ObjectAddress *object,
 static bool stack_address_present_add_flags(const ObjectAddress *object,
 								int flags,
 								ObjectAddressStack *stack);
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
 
 
 /*
@@ -2193,7 +2183,7 @@ getObjectClass(const ObjectAddress *object)
 	/* only pg_class entries can have nonzero objectSubId */
 	if (object->classId != RelationRelationId &&
 		object->objectSubId != 0)
-		elog(ERROR, "invalid objectSubId 0 for object class %u",
+		elog(ERROR, "invalid non-zero objectSubId for object class %u",
 			 object->classId);
 
 	switch (object->classId)
@@ -2297,807 +2287,3 @@ getObjectClass(const ObjectAddress *object)
 	elog(ERROR, "unrecognized object class: %u", object->classId);
 	return OCLASS_CLASS;		/* keep compiler quiet */
 }
-
-/*
- * getObjectDescription: build an object description for messages
- *
- * The result is a palloc'd string.
- */
-char *
-getObjectDescription(const ObjectAddress *object)
-{
-	StringInfoData buffer;
-
-	initStringInfo(&buffer);
-
-	switch (getObjectClass(object))
-	{
-		case OCLASS_CLASS:
-			getRelationDescription(&buffer, object->objectId);
-			if (object->objectSubId != 0)
-				appendStringInfo(&buffer, _(" column %s"),
-								 get_relid_attribute_name(object->objectId,
-													   object->objectSubId));
-			break;
-
-		case OCLASS_PROC:
-			appendStringInfo(&buffer, _("function %s"),
-							 format_procedure(object->objectId));
-			break;
-
-		case OCLASS_TYPE:
-			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
-			break;
-
-		case OCLASS_CAST:
-			{
-				Relation	castDesc;
-				ScanKeyData skey[1];
-				SysScanDesc rcscan;
-				HeapTuple	tup;
-				Form_pg_cast castForm;
-
-				castDesc = heap_open(CastRelationId, AccessShareLock);
-
-				ScanKeyInit(&skey[0],
-							ObjectIdAttributeNumber,
-							BTEqualStrategyNumber, F_OIDEQ,
-							ObjectIdGetDatum(object->objectId));
-
-				rcscan = systable_beginscan(castDesc, CastOidIndexId, true,
-											SnapshotNow, 1, skey);
-
-				tup = systable_getnext(rcscan);
-
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
-
-				castForm = (Form_pg_cast) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("cast from %s to %s"),
-								 format_type_be(castForm->castsource),
-								 format_type_be(castForm->casttarget));
-
-				systable_endscan(rcscan);
-				heap_close(castDesc, AccessShareLock);
-				break;
-			}
-
-		case OCLASS_COLLATION:
-			{
-				HeapTuple	collTup;
-				Form_pg_collation coll;
-
-				collTup = SearchSysCache1(COLLOID,
-										  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
-				coll = (Form_pg_collation) GETSTRUCT(collTup);
-				appendStringInfo(&buffer, _("collation %s"),
-								 NameStr(coll->collname));
-				ReleaseSysCache(collTup);
-				break;
-			}
-
-		case OCLASS_CONSTRAINT:
-			{
-				HeapTuple	conTup;
-				Form_pg_constraint con;
-
-				conTup = SearchSysCache1(CONSTROID,
-										 ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
-				con = (Form_pg_constraint) GETSTRUCT(conTup);
-
-				if (OidIsValid(con->conrelid))
-				{
-					StringInfoData rel;
-
-					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
-					appendStringInfo(&buffer, _("constraint %s on %s"),
-									 NameStr(con->conname), rel.data);
-					pfree(rel.data);
-				}
-				else
-				{
-					appendStringInfo(&buffer, _("constraint %s"),
-									 NameStr(con->conname));
-				}
-
-				ReleaseSysCache(conTup);
-				break;
-			}
-
-		case OCLASS_CONVERSION:
-			{
-				HeapTuple	conTup;
-
-				conTup = SearchSysCache1(CONVOID,
-										 ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
-				appendStringInfo(&buffer, _("conversion %s"),
-				 NameStr(((Form_pg_conversion) GETSTRUCT(conTup))->conname));
-				ReleaseSysCache(conTup);
-				break;
-			}
-
-		case OCLASS_DEFAULT:
-			{
-				Relation	attrdefDesc;
-				ScanKeyData skey[1];
-				SysScanDesc adscan;
-				HeapTuple	tup;
-				Form_pg_attrdef attrdef;
-				ObjectAddress colobject;
-
-				attrdefDesc = heap_open(AttrDefaultRelationId, AccessShareLock);
-
-				ScanKeyInit(&skey[0],
-							ObjectIdAttributeNumber,
-							BTEqualStrategyNumber, F_OIDEQ,
-							ObjectIdGetDatum(object->objectId));
-
-				adscan = systable_beginscan(attrdefDesc, AttrDefaultOidIndexId,
-											true, SnapshotNow, 1, skey);
-
-				tup = systable_getnext(adscan);
-
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
-
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
-
-				appendStringInfo(&buffer, _("default for %s"),
-								 getObjectDescription(&colobject));
-
-				systable_endscan(adscan);
-				heap_close(attrdefDesc, AccessShareLock);
-				break;
-			}
-
-		case OCLASS_LANGUAGE:
-			{
-				HeapTuple	langTup;
-
-				langTup = SearchSysCache1(LANGOID,
-										  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
-				appendStringInfo(&buffer, _("language %s"),
-				  NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname));
-				ReleaseSysCache(langTup);
-				break;
-			}
-		case OCLASS_LARGEOBJECT:
-			appendStringInfo(&buffer, _("large object %u"),
-							 object->objectId);
-			break;
-
-		case OCLASS_OPERATOR:
-			appendStringInfo(&buffer, _("operator %s"),
-							 format_operator(object->objectId));
-			break;
-
-		case OCLASS_OPCLASS:
-			{
-				HeapTuple	opcTup;
-				Form_pg_opclass opcForm;
-				HeapTuple	amTup;
-				Form_pg_am	amForm;
-				char	   *nspname;
-
-				opcTup = SearchSysCache1(CLAOID,
-										 ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
-				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
-
-				amTup = SearchSysCache1(AMOID,
-										ObjectIdGetDatum(opcForm->opcmethod));
-				if (!HeapTupleIsValid(amTup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 opcForm->opcmethod);
-				amForm = (Form_pg_am) GETSTRUCT(amTup);
-
-				/* Qualify the name if not visible in search path */
-				if (OpclassIsVisible(object->objectId))
-					nspname = NULL;
-				else
-					nspname = get_namespace_name(opcForm->opcnamespace);
-
-				appendStringInfo(&buffer, _("operator class %s for access method %s"),
-								 quote_qualified_identifier(nspname,
-												  NameStr(opcForm->opcname)),
-								 NameStr(amForm->amname));
-
-				ReleaseSysCache(amTup);
-				ReleaseSysCache(opcTup);
-				break;
-			}
-
-		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
-			break;
-
-		case OCLASS_AMOP:
-			{
-				Relation	amopDesc;
-				ScanKeyData skey[1];
-				SysScanDesc amscan;
-				HeapTuple	tup;
-				Form_pg_amop amopForm;
-				StringInfoData opfam;
-
-				amopDesc = heap_open(AccessMethodOperatorRelationId,
-									 AccessShareLock);
-
-				ScanKeyInit(&skey[0],
-							ObjectIdAttributeNumber,
-							BTEqualStrategyNumber, F_OIDEQ,
-							ObjectIdGetDatum(object->objectId));
-
-				amscan = systable_beginscan(amopDesc, AccessMethodOperatorOidIndexId, true,
-											SnapshotNow, 1, skey);
-
-				tup = systable_getnext(amscan);
-
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
-
-				amopForm = (Form_pg_amop) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
-
-				/*------
-				   translator: %d is the operator strategy (a number), the
-				   first two %s's are data type names, the third %s is the
-				   description of the operator family, and the last %s is the
-				   textual form of the operator with arguments.  */
-				appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
-								 amopForm->amopstrategy,
-								 format_type_be(amopForm->amoplefttype),
-								 format_type_be(amopForm->amoprighttype),
-								 opfam.data,
-								 format_operator(amopForm->amopopr));
-
-				pfree(opfam.data);
-
-				systable_endscan(amscan);
-				heap_close(amopDesc, AccessShareLock);
-				break;
-			}
-
-		case OCLASS_AMPROC:
-			{
-				Relation	amprocDesc;
-				ScanKeyData skey[1];
-				SysScanDesc amscan;
-				HeapTuple	tup;
-				Form_pg_amproc amprocForm;
-				StringInfoData opfam;
-
-				amprocDesc = heap_open(AccessMethodProcedureRelationId,
-									   AccessShareLock);
-
-				ScanKeyInit(&skey[0],
-							ObjectIdAttributeNumber,
-							BTEqualStrategyNumber, F_OIDEQ,
-							ObjectIdGetDatum(object->objectId));
-
-				amscan = systable_beginscan(amprocDesc, AccessMethodProcedureOidIndexId, true,
-											SnapshotNow, 1, skey);
-
-				tup = systable_getnext(amscan);
-
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
-
-				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
-
-				/*------
-				   translator: %d is the function number, the first two %s's
-				   are data type names, the third %s is the description of the
-				   operator family, and the last %s is the textual form of the
-				   function with arguments.  */
-				appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
-								 amprocForm->amprocnum,
-								 format_type_be(amprocForm->amproclefttype),
-								 format_type_be(amprocForm->amprocrighttype),
-								 opfam.data,
-								 format_procedure(amprocForm->amproc));
-
-				pfree(opfam.data);
-
-				systable_endscan(amscan);
-				heap_close(amprocDesc, AccessShareLock);
-				break;
-			}
-
-		case OCLASS_REWRITE:
-			{
-				Relation	ruleDesc;
-				ScanKeyData skey[1];
-				SysScanDesc rcscan;
-				HeapTuple	tup;
-				Form_pg_rewrite rule;
-
-				ruleDesc = heap_open(RewriteRelationId, AccessShareLock);
-
-				ScanKeyInit(&skey[0],
-							ObjectIdAttributeNumber,
-							BTEqualStrategyNumber, F_OIDEQ,
-							ObjectIdGetDatum(object->objectId));
-
-				rcscan = systable_beginscan(ruleDesc, RewriteOidIndexId, true,
-											SnapshotNow, 1, skey);
-
-				tup = systable_getnext(rcscan);
-
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
-
-				rule = (Form_pg_rewrite) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("rule %s on "),
-								 NameStr(rule->rulename));
-				getRelationDescription(&buffer, rule->ev_class);
-
-				systable_endscan(rcscan);
-				heap_close(ruleDesc, AccessShareLock);
-				break;
-			}
-
-		case OCLASS_TRIGGER:
-			{
-				Relation	trigDesc;
-				ScanKeyData skey[1];
-				SysScanDesc tgscan;
-				HeapTuple	tup;
-				Form_pg_trigger trig;
-
-				trigDesc = heap_open(TriggerRelationId, AccessShareLock);
-
-				ScanKeyInit(&skey[0],
-							ObjectIdAttributeNumber,
-							BTEqualStrategyNumber, F_OIDEQ,
-							ObjectIdGetDatum(object->objectId));
-
-				tgscan = systable_beginscan(trigDesc, TriggerOidIndexId, true,
-											SnapshotNow, 1, skey);
-
-				tup = systable_getnext(tgscan);
-
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
-
-				trig = (Form_pg_trigger) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("trigger %s on "),
-								 NameStr(trig->tgname));
-				getRelationDescription(&buffer, trig->tgrelid);
-
-				systable_endscan(tgscan);
-				heap_close(trigDesc, AccessShareLock);
-				break;
-			}
-
-		case OCLASS_SCHEMA:
-			{
-				char	   *nspname;
-
-				nspname = get_namespace_name(object->objectId);
-				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
-				appendStringInfo(&buffer, _("schema %s"), nspname);
-				break;
-			}
-
-		case OCLASS_TSPARSER:
-			{
-				HeapTuple	tup;
-
-				tup = SearchSysCache1(TSPARSEROID,
-									  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
-				appendStringInfo(&buffer, _("text search parser %s"),
-					 NameStr(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname));
-				ReleaseSysCache(tup);
-				break;
-			}
-
-		case OCLASS_TSDICT:
-			{
-				HeapTuple	tup;
-
-				tup = SearchSysCache1(TSDICTOID,
-									  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
-				appendStringInfo(&buffer, _("text search dictionary %s"),
-					  NameStr(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname));
-				ReleaseSysCache(tup);
-				break;
-			}
-
-		case OCLASS_TSTEMPLATE:
-			{
-				HeapTuple	tup;
-
-				tup = SearchSysCache1(TSTEMPLATEOID,
-									  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
-				appendStringInfo(&buffer, _("text search template %s"),
-				  NameStr(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname));
-				ReleaseSysCache(tup);
-				break;
-			}
-
-		case OCLASS_TSCONFIG:
-			{
-				HeapTuple	tup;
-
-				tup = SearchSysCache1(TSCONFIGOID,
-									  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
-				appendStringInfo(&buffer, _("text search configuration %s"),
-					 NameStr(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname));
-				ReleaseSysCache(tup);
-				break;
-			}
-
-		case OCLASS_ROLE:
-			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId));
-				break;
-			}
-
-		case OCLASS_DATABASE:
-			{
-				char	   *datname;
-
-				datname = get_database_name(object->objectId);
-				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
-				appendStringInfo(&buffer, _("database %s"), datname);
-				break;
-			}
-
-		case OCLASS_TBLSPACE:
-			{
-				char	   *tblspace;
-
-				tblspace = get_tablespace_name(object->objectId);
-				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
-				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
-				break;
-			}
-
-		case OCLASS_FDW:
-			{
-				ForeignDataWrapper *fdw;
-
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
-				break;
-			}
-
-		case OCLASS_FOREIGN_SERVER:
-			{
-				ForeignServer *srv;
-
-				srv = GetForeignServer(object->objectId);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
-				break;
-			}
-
-		case OCLASS_USER_MAPPING:
-			{
-				HeapTuple	tup;
-				Oid			useid;
-				char	   *usename;
-
-				tup = SearchSysCache1(USERMAPPINGOID,
-									  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
-
-				useid = ((Form_pg_user_mapping) GETSTRUCT(tup))->umuser;
-
-				ReleaseSysCache(tup);
-
-				if (OidIsValid(useid))
-					usename = GetUserNameFromId(useid);
-				else
-					usename = "public";
-
-				appendStringInfo(&buffer, _("user mapping for %s"), usename);
-				break;
-			}
-
-		case OCLASS_DEFACL:
-			{
-				Relation	defaclrel;
-				ScanKeyData skey[1];
-				SysScanDesc rcscan;
-				HeapTuple	tup;
-				Form_pg_default_acl defacl;
-
-				defaclrel = heap_open(DefaultAclRelationId, AccessShareLock);
-
-				ScanKeyInit(&skey[0],
-							ObjectIdAttributeNumber,
-							BTEqualStrategyNumber, F_OIDEQ,
-							ObjectIdGetDatum(object->objectId));
-
-				rcscan = systable_beginscan(defaclrel, DefaultAclOidIndexId,
-											true, SnapshotNow, 1, skey);
-
-				tup = systable_getnext(rcscan);
-
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
-
-				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
-
-				switch (defacl->defaclobjtype)
-				{
-					case DEFACLOBJ_RELATION:
-						appendStringInfo(&buffer,
-										 _("default privileges on new relations belonging to role %s"),
-									  GetUserNameFromId(defacl->defaclrole));
-						break;
-					case DEFACLOBJ_SEQUENCE:
-						appendStringInfo(&buffer,
-										 _("default privileges on new sequences belonging to role %s"),
-									  GetUserNameFromId(defacl->defaclrole));
-						break;
-					case DEFACLOBJ_FUNCTION:
-						appendStringInfo(&buffer,
-										 _("default privileges on new functions belonging to role %s"),
-									  GetUserNameFromId(defacl->defaclrole));
-						break;
-					case DEFACLOBJ_TYPE:
-						appendStringInfo(&buffer,
-										 _("default privileges on new types belonging to role %s"),
-									  GetUserNameFromId(defacl->defaclrole));
-						break;
-					default:
-						/* shouldn't get here */
-						appendStringInfo(&buffer,
-								_("default privileges belonging to role %s"),
-									  GetUserNameFromId(defacl->defaclrole));
-						break;
-				}
-
-				if (OidIsValid(defacl->defaclnamespace))
-				{
-					appendStringInfo(&buffer,
-									 _(" in schema %s"),
-								get_namespace_name(defacl->defaclnamespace));
-				}
-
-				systable_endscan(rcscan);
-				heap_close(defaclrel, AccessShareLock);
-				break;
-			}
-
-		case OCLASS_EXTENSION:
-			{
-				char	   *extname;
-
-				extname = get_extension_name(object->objectId);
-				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
-				appendStringInfo(&buffer, _("extension %s"), extname);
-				break;
-			}
-
-        case OCLASS_EVENT_TRIGGER:
-			{
-				HeapTuple	tup;
-
-				tup = SearchSysCache1(EVENTTRIGGEROID,
-									  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
-				appendStringInfo(&buffer, _("event trigger %s"),
-					 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
-				ReleaseSysCache(tup);
-				break;
-			}
-
-		default:
-			appendStringInfo(&buffer, "unrecognized object %u %u %d",
-							 object->classId,
-							 object->objectId,
-							 object->objectSubId);
-			break;
-	}
-
-	return buffer.data;
-}
-
-/*
- * getObjectDescriptionOids: as above, except the object is specified by Oids
- */
-char *
-getObjectDescriptionOids(Oid classid, Oid objid)
-{
-	ObjectAddress address;
-
-	address.classId = classid;
-	address.objectId = objid;
-	address.objectSubId = 0;
-
-	return getObjectDescription(&address);
-}
-
-/*
- * subroutine for getObjectDescription: describe a relation
- */
-static void
-getRelationDescription(StringInfo buffer, Oid relid)
-{
-	HeapTuple	relTup;
-	Form_pg_class relForm;
-	char	   *nspname;
-	char	   *relname;
-
-	relTup = SearchSysCache1(RELOID,
-							 ObjectIdGetDatum(relid));
-	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
-	relForm = (Form_pg_class) GETSTRUCT(relTup);
-
-	/* Qualify the name if not visible in search path */
-	if (RelationIsVisible(relid))
-		nspname = NULL;
-	else
-		nspname = get_namespace_name(relForm->relnamespace);
-
-	relname = quote_qualified_identifier(nspname, NameStr(relForm->relname));
-
-	switch (relForm->relkind)
-	{
-		case RELKIND_RELATION:
-			appendStringInfo(buffer, _("table %s"),
-							 relname);
-			break;
-		case RELKIND_INDEX:
-			appendStringInfo(buffer, _("index %s"),
-							 relname);
-			break;
-		case RELKIND_SEQUENCE:
-			appendStringInfo(buffer, _("sequence %s"),
-							 relname);
-			break;
-		case RELKIND_TOASTVALUE:
-			appendStringInfo(buffer, _("toast table %s"),
-							 relname);
-			break;
-		case RELKIND_VIEW:
-			appendStringInfo(buffer, _("view %s"),
-							 relname);
-			break;
-		case RELKIND_MATVIEW:
-			appendStringInfo(buffer, _("materialized view %s"),
-							 relname);
-			break;
-		case RELKIND_COMPOSITE_TYPE:
-			appendStringInfo(buffer, _("composite type %s"),
-							 relname);
-			break;
-		case RELKIND_FOREIGN_TABLE:
-			appendStringInfo(buffer, _("foreign table %s"),
-							 relname);
-			break;
-		default:
-			/* shouldn't get here */
-			appendStringInfo(buffer, _("relation %s"),
-							 relname);
-			break;
-	}
-
-	ReleaseSysCache(relTup);
-}
-
-/*
- * subroutine for getObjectDescription: describe an operator family
- */
-static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
-{
-	HeapTuple	opfTup;
-	Form_pg_opfamily opfForm;
-	HeapTuple	amTup;
-	Form_pg_am	amForm;
-	char	   *nspname;
-
-	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
-	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
-	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
-
-	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
-	if (!HeapTupleIsValid(amTup))
-		elog(ERROR, "cache lookup failed for access method %u",
-			 opfForm->opfmethod);
-	amForm = (Form_pg_am) GETSTRUCT(amTup);
-
-	/* Qualify the name if not visible in search path */
-	if (OpfamilyIsVisible(opfid))
-		nspname = NULL;
-	else
-		nspname = get_namespace_name(opfForm->opfnamespace);
-
-	appendStringInfo(buffer, _("operator family %s for access method %s"),
-					 quote_qualified_identifier(nspname,
-												NameStr(opfForm->opfname)),
-					 NameStr(amForm->amname));
-
-	ReleaseSysCache(amTup);
-	ReleaseSysCache(opfTup);
-}
-
-/*
- * SQL-level callable version of getObjectDescription
- */
-Datum
-pg_describe_object(PG_FUNCTION_ARGS)
-{
-	Oid			classid = PG_GETARG_OID(0);
-	Oid			objid = PG_GETARG_OID(1);
-	int32		subobjid = PG_GETARG_INT32(2);
-	char	   *description = NULL;
-	ObjectAddress address;
-
-	/* for "pinned" items in pg_depend, return null */
-	if (!OidIsValid(classid) && !OidIsValid(objid))
-		PG_RETURN_NULL();
-
-	address.classId = classid;
-	address.objectId = objid;
-	address.objectSubId = subobjid;
-
-	description = getObjectDescription(&address);
-	PG_RETURN_TEXT_P(cstring_to_text(description));
-}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 6f60d7cad1ae14f7e098a5d9c475072e1747539f..48ef6bf0a490530f1bcbe5670129f5bce4d6aa89 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -20,8 +20,12 @@
 #include "catalog/catalog.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaddress.h"
+#include "catalog/pg_amop.h"
+#include "catalog/pg_amproc.h"
+#include "catalog/pg_attrdef.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
+#include "catalog/pg_default_acl.h"
 #include "catalog/pg_event_trigger.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -46,6 +50,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_user_mapping.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
@@ -54,6 +59,7 @@
 #include "commands/tablespace.h"
 #include "commands/trigger.h"
 #include "foreign/foreign.h"
+#include "funcapi.h"
 #include "libpq/be-fsstubs.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -88,6 +94,10 @@ typedef struct
 	AttrNumber	attnum_owner;	/* attnum of owner field */
 	AttrNumber	attnum_acl;		/* attnum of acl field */
 	AclObjectKind acl_kind;		/* ACL_KIND_* of this object type */
+	bool		is_nsp_name_unique;	/* can the nsp/name combination (or name
+									 * alone, if there's no namespace) be
+									 * considered an unique identifier for an
+									 * object of this class? */
 } ObjectPropertyType;
 
 static ObjectPropertyType ObjectProperty[] =
@@ -101,7 +111,8 @@ static ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,
 		InvalidAttrNumber,
 		InvalidAttrNumber,
-		-1
+		-1,
+		false
 	},
 	{
 		CollationRelationId,
@@ -112,7 +123,8 @@ static ObjectPropertyType ObjectProperty[] =
 		Anum_pg_collation_collnamespace,
 		Anum_pg_collation_collowner,
 		InvalidAttrNumber,
-		ACL_KIND_COLLATION
+		ACL_KIND_COLLATION,
+		true
 	},
 	{
 		ConstraintRelationId,
@@ -123,7 +135,8 @@ static ObjectPropertyType ObjectProperty[] =
 		Anum_pg_constraint_connamespace,
 		InvalidAttrNumber,
 		InvalidAttrNumber,
-		-1
+		-1,
+		false
 	},
 	{
 		ConversionRelationId,
@@ -134,7 +147,8 @@ static ObjectPropertyType ObjectProperty[] =
 		Anum_pg_conversion_connamespace,
 		Anum_pg_conversion_conowner,
 		InvalidAttrNumber,
-		ACL_KIND_CONVERSION
+		ACL_KIND_CONVERSION,
+		true
 	},
 	{
 		DatabaseRelationId,
@@ -145,7 +159,8 @@ static ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,
 		Anum_pg_database_datdba,
 		Anum_pg_database_datacl,
-		ACL_KIND_DATABASE
+		ACL_KIND_DATABASE,
+		true
 	},
 	{
 		ExtensionRelationId,
@@ -156,7 +171,8 @@ static ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,		/* extension doesn't belong to extnamespace */
 		Anum_pg_extension_extowner,
 		InvalidAttrNumber,
-		ACL_KIND_EXTENSION
+		ACL_KIND_EXTENSION,
+		true
 	},
 	{
 		ForeignDataWrapperRelationId,
@@ -167,7 +183,8 @@ static ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,
 		Anum_pg_foreign_data_wrapper_fdwowner,
 		Anum_pg_foreign_data_wrapper_fdwacl,
-		ACL_KIND_FDW
+		ACL_KIND_FDW,
+		true
 	},
 	{
 		ForeignServerRelationId,
@@ -178,7 +195,8 @@ static ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,
 		Anum_pg_foreign_server_srvowner,
 		Anum_pg_foreign_server_srvacl,
-		ACL_KIND_FOREIGN_SERVER
+		ACL_KIND_FOREIGN_SERVER,
+		true
 	},
 	{
 		ProcedureRelationId,
@@ -189,7 +207,8 @@ static ObjectPropertyType ObjectProperty[] =
 		Anum_pg_proc_pronamespace,
 		Anum_pg_proc_proowner,
 		Anum_pg_proc_proacl,
-		ACL_KIND_PROC
+		ACL_KIND_PROC,
+		false
 	},
 	{
 		LanguageRelationId,
@@ -200,7 +219,8 @@ static ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,
 		Anum_pg_language_lanowner,
 		Anum_pg_language_lanacl,
-		ACL_KIND_LANGUAGE
+		ACL_KIND_LANGUAGE,
+		true
 	},
 	{
 		LargeObjectMetadataRelationId,
@@ -211,7 +231,8 @@ static ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,
 		Anum_pg_largeobject_metadata_lomowner,
 		Anum_pg_largeobject_metadata_lomacl,
-		ACL_KIND_LARGEOBJECT
+		ACL_KIND_LARGEOBJECT,
+		false
 	},
 	{
 		OperatorClassRelationId,
@@ -222,7 +243,8 @@ static ObjectPropertyType ObjectProperty[] =
 		Anum_pg_opclass_opcnamespace,
 		Anum_pg_opclass_opcowner,
 		InvalidAttrNumber,
-		ACL_KIND_OPCLASS
+		ACL_KIND_OPCLASS,
+		true
 	},
 	{
 		OperatorRelationId,
@@ -233,7 +255,8 @@ static ObjectPropertyType ObjectProperty[] =
 		Anum_pg_operator_oprnamespace,
 		Anum_pg_operator_oprowner,
 		InvalidAttrNumber,
-		ACL_KIND_OPER
+		ACL_KIND_OPER,
+		false
 	},
 	{
 		OperatorFamilyRelationId,
@@ -244,7 +267,8 @@ static ObjectPropertyType ObjectProperty[] =
 		Anum_pg_opfamily_opfnamespace,
 		Anum_pg_opfamily_opfowner,
 		InvalidAttrNumber,
-		ACL_KIND_OPFAMILY
+		ACL_KIND_OPFAMILY,
+		true
 	},
 	{
 		AuthIdRelationId,
@@ -255,7 +279,8 @@ static ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,
 		InvalidAttrNumber,
 		InvalidAttrNumber,
-		-1
+		-1,
+		true
 	},
 	{
 		RewriteRelationId,
@@ -266,7 +291,8 @@ static ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,
 		InvalidAttrNumber,
 		InvalidAttrNumber,
-		-1
+		-1,
+		false
 	},
 	{
 		NamespaceRelationId,
@@ -277,7 +303,8 @@ static ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,
 		Anum_pg_namespace_nspowner,
 		Anum_pg_namespace_nspacl,
-		ACL_KIND_NAMESPACE
+		ACL_KIND_NAMESPACE,
+		true
 	},
 	{
 		RelationRelationId,
@@ -288,7 +315,8 @@ static ObjectPropertyType ObjectProperty[] =
 		Anum_pg_class_relnamespace,
 		Anum_pg_class_relowner,
 		Anum_pg_class_relacl,
-		ACL_KIND_CLASS
+		ACL_KIND_CLASS,
+		true
 	},
 	{
 		TableSpaceRelationId,
@@ -299,7 +327,8 @@ static ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,
 		Anum_pg_tablespace_spcowner,
 		Anum_pg_tablespace_spcacl,
-		ACL_KIND_TABLESPACE
+		ACL_KIND_TABLESPACE,
+		true
 	},
 	{
 		TriggerRelationId,
@@ -311,6 +340,7 @@ static ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,
 		InvalidAttrNumber,
 		-1,
+		false
 	},
 	{
 		EventTriggerRelationId,
@@ -322,6 +352,7 @@ static ObjectPropertyType ObjectProperty[] =
 		Anum_pg_event_trigger_evtowner,
 		InvalidAttrNumber,
 		ACL_KIND_EVENT_TRIGGER,
+		true
 	},
 	{
 		TSConfigRelationId,
@@ -332,7 +363,8 @@ static ObjectPropertyType ObjectProperty[] =
 		Anum_pg_ts_config_cfgnamespace,
 		Anum_pg_ts_config_cfgowner,
 		InvalidAttrNumber,
-		ACL_KIND_TSCONFIGURATION
+		ACL_KIND_TSCONFIGURATION,
+		true
 	},
 	{
 		TSDictionaryRelationId,
@@ -343,7 +375,8 @@ static ObjectPropertyType ObjectProperty[] =
 		Anum_pg_ts_dict_dictnamespace,
 		Anum_pg_ts_dict_dictowner,
 		InvalidAttrNumber,
-		ACL_KIND_TSDICTIONARY
+		ACL_KIND_TSDICTIONARY,
+		true
 	},
 	{
 		TSParserRelationId,
@@ -355,6 +388,7 @@ static ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,
 		InvalidAttrNumber,
 		-1,
+		true
 	},
 	{
 		TSTemplateRelationId,
@@ -366,6 +400,7 @@ static ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,
 		InvalidAttrNumber,
 		-1,
+		true,
 	},
 	{
 		TypeRelationId,
@@ -376,7 +411,8 @@ static ObjectPropertyType ObjectProperty[] =
 		Anum_pg_type_typnamespace,
 		Anum_pg_type_typowner,
 		Anum_pg_type_typacl,
-		ACL_KIND_TYPE
+		ACL_KIND_TYPE,
+		true
 	}
 };
 
@@ -396,6 +432,15 @@ static ObjectAddress get_object_address_opcf(ObjectType objtype, List *objname,
 						List *objargs, bool missing_ok);
 static ObjectPropertyType *get_object_property_data(Oid class_id);
 
+static void getRelationDescription(StringInfo buffer, Oid relid);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationTypeDescription(StringInfo buffer, Oid relid,
+						   int32 objectSubId);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid);
+static void getRelationIdentity(StringInfo buffer, Oid relid);
+
 /*
  * Translate an object name and arguments (as passed by the parser) to an
  * ObjectAddress.
@@ -1344,6 +1389,32 @@ get_object_aclkind(Oid class_id)
 	return prop->acl_kind;
 }
 
+bool
+get_object_namensp_unique(Oid class_id)
+{
+	ObjectPropertyType *prop = get_object_property_data(class_id);
+
+	return prop->is_nsp_name_unique;
+}
+
+/*
+ * Return whether we have useful data for the given object class in the
+ * ObjectProperty table.
+ */
+bool
+is_objectclass_supported(Oid class_id)
+{
+	int			index;
+
+	for (index = 0; index < lengthof(ObjectProperty); index++)
+	{
+		if (ObjectProperty[index].class_oid == class_id)
+			return true;
+	}
+
+	return false;
+}
+
 /*
  * Find ObjectProperty structure by class_id.
  */
@@ -1374,3 +1445,1919 @@ get_object_property_data(Oid class_id)
 
 	return NULL; /* keep MSC compiler happy */
 }
+
+/*
+ * Return a copy of the tuple for the object with the given object OID, from
+ * the given catalog (which must have been opened by the caller and suitably
+ * locked).  NULL is returned if the OID is not found.
+ *
+ * We try a syscache first, if available.
+ */
+HeapTuple
+get_catalog_object_by_oid(Relation catalog, Oid objectId)
+{
+	HeapTuple	tuple;
+	Oid			classId = RelationGetRelid(catalog);
+	int			oidCacheId = get_object_catcache_oid(classId);
+
+	if (oidCacheId > 0)
+	{
+		tuple = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objectId));
+		if (!HeapTupleIsValid(tuple))  /* should not happen */
+			return NULL;
+	}
+	else
+	{
+		Oid			oidIndexId = get_object_oid_index(classId);
+		SysScanDesc	scan;
+		ScanKeyData	skey;
+
+		Assert(OidIsValid(oidIndexId));
+
+		ScanKeyInit(&skey,
+					ObjectIdAttributeNumber,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(objectId));
+
+		scan = systable_beginscan(catalog, oidIndexId, true,
+								  SnapshotNow, 1, &skey);
+		tuple = systable_getnext(scan);
+		if (!HeapTupleIsValid(tuple))
+		{
+			systable_endscan(scan);
+			return NULL;
+		}
+		tuple = heap_copytuple(tuple);
+
+		systable_endscan(scan);
+	}
+
+	return tuple;
+}
+
+/*
+ * getObjectDescription: build an object description for messages
+ *
+ * The result is a palloc'd string.
+ */
+char *
+getObjectDescription(const ObjectAddress *object)
+{
+	StringInfoData buffer;
+
+	initStringInfo(&buffer);
+
+	switch (getObjectClass(object))
+	{
+		case OCLASS_CLASS:
+			getRelationDescription(&buffer, object->objectId);
+			if (object->objectSubId != 0)
+				appendStringInfo(&buffer, _(" column %s"),
+								 get_relid_attribute_name(object->objectId,
+													   object->objectSubId));
+			break;
+
+		case OCLASS_PROC:
+			appendStringInfo(&buffer, _("function %s"),
+							 format_procedure(object->objectId));
+			break;
+
+		case OCLASS_TYPE:
+			appendStringInfo(&buffer, _("type %s"),
+							 format_type_be(object->objectId));
+			break;
+
+		case OCLASS_CAST:
+			{
+				Relation	castDesc;
+				ScanKeyData skey[1];
+				SysScanDesc rcscan;
+				HeapTuple	tup;
+				Form_pg_cast castForm;
+
+				castDesc = heap_open(CastRelationId, AccessShareLock);
+
+				ScanKeyInit(&skey[0],
+							ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				rcscan = systable_beginscan(castDesc, CastOidIndexId, true,
+											SnapshotNow, 1, skey);
+
+				tup = systable_getnext(rcscan);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for cast %u",
+						 object->objectId);
+
+				castForm = (Form_pg_cast) GETSTRUCT(tup);
+
+				appendStringInfo(&buffer, _("cast from %s to %s"),
+								 format_type_be(castForm->castsource),
+								 format_type_be(castForm->casttarget));
+
+				systable_endscan(rcscan);
+				heap_close(castDesc, AccessShareLock);
+				break;
+			}
+
+		case OCLASS_COLLATION:
+			{
+				HeapTuple	collTup;
+				Form_pg_collation coll;
+
+				collTup = SearchSysCache1(COLLOID,
+										  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(collTup))
+					elog(ERROR, "cache lookup failed for collation %u",
+						 object->objectId);
+				coll = (Form_pg_collation) GETSTRUCT(collTup);
+				appendStringInfo(&buffer, _("collation %s"),
+								 NameStr(coll->collname));
+				ReleaseSysCache(collTup);
+				break;
+			}
+
+		case OCLASS_CONSTRAINT:
+			{
+				HeapTuple	conTup;
+				Form_pg_constraint con;
+
+				conTup = SearchSysCache1(CONSTROID,
+										 ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(conTup))
+					elog(ERROR, "cache lookup failed for constraint %u",
+						 object->objectId);
+				con = (Form_pg_constraint) GETSTRUCT(conTup);
+
+				if (OidIsValid(con->conrelid))
+				{
+					StringInfoData rel;
+
+					initStringInfo(&rel);
+					getRelationDescription(&rel, con->conrelid);
+					appendStringInfo(&buffer, _("constraint %s on %s"),
+									 NameStr(con->conname), rel.data);
+					pfree(rel.data);
+				}
+				else
+				{
+					appendStringInfo(&buffer, _("constraint %s"),
+									 NameStr(con->conname));
+				}
+
+				ReleaseSysCache(conTup);
+				break;
+			}
+
+		case OCLASS_CONVERSION:
+			{
+				HeapTuple	conTup;
+
+				conTup = SearchSysCache1(CONVOID,
+										 ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(conTup))
+					elog(ERROR, "cache lookup failed for conversion %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("conversion %s"),
+				 NameStr(((Form_pg_conversion) GETSTRUCT(conTup))->conname));
+				ReleaseSysCache(conTup);
+				break;
+			}
+
+		case OCLASS_DEFAULT:
+			{
+				Relation	attrdefDesc;
+				ScanKeyData skey[1];
+				SysScanDesc adscan;
+				HeapTuple	tup;
+				Form_pg_attrdef attrdef;
+				ObjectAddress colobject;
+
+				attrdefDesc = heap_open(AttrDefaultRelationId, AccessShareLock);
+
+				ScanKeyInit(&skey[0],
+							ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				adscan = systable_beginscan(attrdefDesc, AttrDefaultOidIndexId,
+											true, SnapshotNow, 1, skey);
+
+				tup = systable_getnext(adscan);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for attrdef %u",
+						 object->objectId);
+
+				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+
+				colobject.classId = RelationRelationId;
+				colobject.objectId = attrdef->adrelid;
+				colobject.objectSubId = attrdef->adnum;
+
+				appendStringInfo(&buffer, _("default for %s"),
+								 getObjectDescription(&colobject));
+
+				systable_endscan(adscan);
+				heap_close(attrdefDesc, AccessShareLock);
+				break;
+			}
+
+		case OCLASS_LANGUAGE:
+			{
+				HeapTuple	langTup;
+
+				langTup = SearchSysCache1(LANGOID,
+										  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(langTup))
+					elog(ERROR, "cache lookup failed for language %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("language %s"),
+				  NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname));
+				ReleaseSysCache(langTup);
+				break;
+			}
+		case OCLASS_LARGEOBJECT:
+			appendStringInfo(&buffer, _("large object %u"),
+							 object->objectId);
+			break;
+
+		case OCLASS_OPERATOR:
+			appendStringInfo(&buffer, _("operator %s"),
+							 format_operator(object->objectId));
+			break;
+
+		case OCLASS_OPCLASS:
+			{
+				HeapTuple	opcTup;
+				Form_pg_opclass opcForm;
+				HeapTuple	amTup;
+				Form_pg_am	amForm;
+				char	   *nspname;
+
+				opcTup = SearchSysCache1(CLAOID,
+										 ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(opcTup))
+					elog(ERROR, "cache lookup failed for opclass %u",
+						 object->objectId);
+				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
+
+				amTup = SearchSysCache1(AMOID,
+										ObjectIdGetDatum(opcForm->opcmethod));
+				if (!HeapTupleIsValid(amTup))
+					elog(ERROR, "cache lookup failed for access method %u",
+						 opcForm->opcmethod);
+				amForm = (Form_pg_am) GETSTRUCT(amTup);
+
+				/* Qualify the name if not visible in search path */
+				if (OpclassIsVisible(object->objectId))
+					nspname = NULL;
+				else
+					nspname = get_namespace_name(opcForm->opcnamespace);
+
+				appendStringInfo(&buffer, _("operator class %s for access method %s"),
+								 quote_qualified_identifier(nspname,
+												  NameStr(opcForm->opcname)),
+								 NameStr(amForm->amname));
+
+				ReleaseSysCache(amTup);
+				ReleaseSysCache(opcTup);
+				break;
+			}
+
+		case OCLASS_OPFAMILY:
+			getOpFamilyDescription(&buffer, object->objectId);
+			break;
+
+		case OCLASS_AMOP:
+			{
+				Relation	amopDesc;
+				HeapTuple	tup;
+				ScanKeyData skey[1];
+				SysScanDesc amscan;
+				Form_pg_amop amopForm;
+				StringInfoData opfam;
+
+				amopDesc = heap_open(AccessMethodOperatorRelationId,
+									 AccessShareLock);
+
+				ScanKeyInit(&skey[0],
+							ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				amscan = systable_beginscan(amopDesc, AccessMethodOperatorOidIndexId, true,
+											SnapshotNow, 1, skey);
+
+				tup = systable_getnext(amscan);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for amop entry %u",
+						 object->objectId);
+
+				amopForm = (Form_pg_amop) GETSTRUCT(tup);
+
+				initStringInfo(&opfam);
+				getOpFamilyDescription(&opfam, amopForm->amopfamily);
+
+				/*------
+				   translator: %d is the operator strategy (a number), the
+				   first two %s's are data type names, the third %s is the
+				   description of the operator family, and the last %s is the
+				   textual form of the operator with arguments.  */
+				appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
+								 amopForm->amopstrategy,
+								 format_type_be(amopForm->amoplefttype),
+								 format_type_be(amopForm->amoprighttype),
+								 opfam.data,
+								 format_operator(amopForm->amopopr));
+
+				pfree(opfam.data);
+
+				systable_endscan(amscan);
+				heap_close(amopDesc, AccessShareLock);
+				break;
+			}
+
+		case OCLASS_AMPROC:
+			{
+				Relation	amprocDesc;
+				ScanKeyData skey[1];
+				SysScanDesc amscan;
+				HeapTuple	tup;
+				Form_pg_amproc amprocForm;
+				StringInfoData opfam;
+
+				amprocDesc = heap_open(AccessMethodProcedureRelationId,
+									   AccessShareLock);
+
+				ScanKeyInit(&skey[0],
+							ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				amscan = systable_beginscan(amprocDesc, AccessMethodProcedureOidIndexId, true,
+											SnapshotNow, 1, skey);
+
+				tup = systable_getnext(amscan);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for amproc entry %u",
+						 object->objectId);
+
+				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
+
+				initStringInfo(&opfam);
+				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
+
+				/*------
+				   translator: %d is the function number, the first two %s's
+				   are data type names, the third %s is the description of the
+				   operator family, and the last %s is the textual form of the
+				   function with arguments.  */
+				appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
+								 amprocForm->amprocnum,
+								 format_type_be(amprocForm->amproclefttype),
+								 format_type_be(amprocForm->amprocrighttype),
+								 opfam.data,
+								 format_procedure(amprocForm->amproc));
+
+				pfree(opfam.data);
+
+				systable_endscan(amscan);
+				heap_close(amprocDesc, AccessShareLock);
+				break;
+			}
+
+		case OCLASS_REWRITE:
+			{
+				Relation	ruleDesc;
+				ScanKeyData skey[1];
+				SysScanDesc rcscan;
+				HeapTuple	tup;
+				Form_pg_rewrite rule;
+
+				ruleDesc = heap_open(RewriteRelationId, AccessShareLock);
+
+				ScanKeyInit(&skey[0],
+							ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				rcscan = systable_beginscan(ruleDesc, RewriteOidIndexId, true,
+											SnapshotNow, 1, skey);
+
+				tup = systable_getnext(rcscan);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for rule %u",
+						 object->objectId);
+
+				rule = (Form_pg_rewrite) GETSTRUCT(tup);
+
+				appendStringInfo(&buffer, _("rule %s on "),
+								 NameStr(rule->rulename));
+				getRelationDescription(&buffer, rule->ev_class);
+
+				systable_endscan(rcscan);
+				heap_close(ruleDesc, AccessShareLock);
+				break;
+			}
+
+		case OCLASS_TRIGGER:
+			{
+				Relation	trigDesc;
+				ScanKeyData skey[1];
+				SysScanDesc tgscan;
+				HeapTuple	tup;
+				Form_pg_trigger trig;
+
+				trigDesc = heap_open(TriggerRelationId, AccessShareLock);
+
+				ScanKeyInit(&skey[0],
+							ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				tgscan = systable_beginscan(trigDesc, TriggerOidIndexId, true,
+											SnapshotNow, 1, skey);
+
+				tup = systable_getnext(tgscan);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for trigger %u",
+						 object->objectId);
+
+				trig = (Form_pg_trigger) GETSTRUCT(tup);
+
+				appendStringInfo(&buffer, _("trigger %s on "),
+								 NameStr(trig->tgname));
+				getRelationDescription(&buffer, trig->tgrelid);
+
+				systable_endscan(tgscan);
+				heap_close(trigDesc, AccessShareLock);
+				break;
+			}
+
+		case OCLASS_SCHEMA:
+			{
+				char	   *nspname;
+
+				nspname = get_namespace_name(object->objectId);
+				if (!nspname)
+					elog(ERROR, "cache lookup failed for namespace %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("schema %s"), nspname);
+				break;
+			}
+
+		case OCLASS_TSPARSER:
+			{
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(TSPARSEROID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for text search parser %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("text search parser %s"),
+					 NameStr(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_TSDICT:
+			{
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(TSDICTOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for text search dictionary %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("text search dictionary %s"),
+					  NameStr(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_TSTEMPLATE:
+			{
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(TSTEMPLATEOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for text search template %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("text search template %s"),
+				  NameStr(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_TSCONFIG:
+			{
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(TSCONFIGOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for text search configuration %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("text search configuration %s"),
+					 NameStr(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_ROLE:
+			{
+				appendStringInfo(&buffer, _("role %s"),
+								 GetUserNameFromId(object->objectId));
+				break;
+			}
+
+		case OCLASS_DATABASE:
+			{
+				char	   *datname;
+
+				datname = get_database_name(object->objectId);
+				if (!datname)
+					elog(ERROR, "cache lookup failed for database %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("database %s"), datname);
+				break;
+			}
+
+		case OCLASS_TBLSPACE:
+			{
+				char	   *tblspace;
+
+				tblspace = get_tablespace_name(object->objectId);
+				if (!tblspace)
+					elog(ERROR, "cache lookup failed for tablespace %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
+				break;
+			}
+
+		case OCLASS_FDW:
+			{
+				ForeignDataWrapper *fdw;
+
+				fdw = GetForeignDataWrapper(object->objectId);
+				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				break;
+			}
+
+		case OCLASS_FOREIGN_SERVER:
+			{
+				ForeignServer *srv;
+
+				srv = GetForeignServer(object->objectId);
+				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				break;
+			}
+
+		case OCLASS_USER_MAPPING:
+			{
+				HeapTuple	tup;
+				Oid			useid;
+				char	   *usename;
+
+				tup = SearchSysCache1(USERMAPPINGOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for user mapping %u",
+						 object->objectId);
+
+				useid = ((Form_pg_user_mapping) GETSTRUCT(tup))->umuser;
+
+				ReleaseSysCache(tup);
+
+				if (OidIsValid(useid))
+					usename = GetUserNameFromId(useid);
+				else
+					usename = "public";
+
+				appendStringInfo(&buffer, _("user mapping for %s"), usename);
+				break;
+			}
+
+		case OCLASS_DEFACL:
+			{
+				Relation	defaclrel;
+				ScanKeyData skey[1];
+				SysScanDesc rcscan;
+				HeapTuple	tup;
+				Form_pg_default_acl defacl;
+
+				defaclrel = heap_open(DefaultAclRelationId, AccessShareLock);
+
+				ScanKeyInit(&skey[0],
+							ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				rcscan = systable_beginscan(defaclrel, DefaultAclOidIndexId,
+											true, SnapshotNow, 1, skey);
+
+				tup = systable_getnext(rcscan);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for default ACL %u",
+						 object->objectId);
+
+				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
+
+				switch (defacl->defaclobjtype)
+				{
+					case DEFACLOBJ_RELATION:
+						appendStringInfo(&buffer,
+										 _("default privileges on new relations belonging to role %s"),
+									  GetUserNameFromId(defacl->defaclrole));
+						break;
+					case DEFACLOBJ_SEQUENCE:
+						appendStringInfo(&buffer,
+										 _("default privileges on new sequences belonging to role %s"),
+									  GetUserNameFromId(defacl->defaclrole));
+						break;
+					case DEFACLOBJ_FUNCTION:
+						appendStringInfo(&buffer,
+										 _("default privileges on new functions belonging to role %s"),
+									  GetUserNameFromId(defacl->defaclrole));
+						break;
+					case DEFACLOBJ_TYPE:
+						appendStringInfo(&buffer,
+										 _("default privileges on new types belonging to role %s"),
+									  GetUserNameFromId(defacl->defaclrole));
+						break;
+					default:
+						/* shouldn't get here */
+						appendStringInfo(&buffer,
+								_("default privileges belonging to role %s"),
+									  GetUserNameFromId(defacl->defaclrole));
+						break;
+				}
+
+				if (OidIsValid(defacl->defaclnamespace))
+				{
+					appendStringInfo(&buffer,
+									 _(" in schema %s"),
+								get_namespace_name(defacl->defaclnamespace));
+				}
+
+				systable_endscan(rcscan);
+				heap_close(defaclrel, AccessShareLock);
+				break;
+			}
+
+		case OCLASS_EXTENSION:
+			{
+				char	   *extname;
+
+				extname = get_extension_name(object->objectId);
+				if (!extname)
+					elog(ERROR, "cache lookup failed for extension %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("extension %s"), extname);
+				break;
+			}
+
+        case OCLASS_EVENT_TRIGGER:
+			{
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(EVENTTRIGGEROID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for event trigger %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("event trigger %s"),
+					 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		default:
+			appendStringInfo(&buffer, "unrecognized object %u %u %d",
+							 object->classId,
+							 object->objectId,
+							 object->objectSubId);
+			break;
+	}
+
+	return buffer.data;
+}
+
+/*
+ * getObjectDescriptionOids: as above, except the object is specified by Oids
+ */
+char *
+getObjectDescriptionOids(Oid classid, Oid objid)
+{
+	ObjectAddress address;
+
+	address.classId = classid;
+	address.objectId = objid;
+	address.objectSubId = 0;
+
+	return getObjectDescription(&address);
+}
+
+/*
+ * subroutine for getObjectDescription: describe a relation
+ */
+static void
+getRelationDescription(StringInfo buffer, Oid relid)
+{
+	HeapTuple	relTup;
+	Form_pg_class relForm;
+	char	   *nspname;
+	char	   *relname;
+
+	relTup = SearchSysCache1(RELOID,
+							 ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(relTup))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+	relForm = (Form_pg_class) GETSTRUCT(relTup);
+
+	/* Qualify the name if not visible in search path */
+	if (RelationIsVisible(relid))
+		nspname = NULL;
+	else
+		nspname = get_namespace_name(relForm->relnamespace);
+
+	relname = quote_qualified_identifier(nspname, NameStr(relForm->relname));
+
+	switch (relForm->relkind)
+	{
+		case RELKIND_RELATION:
+			appendStringInfo(buffer, _("table %s"),
+							 relname);
+			break;
+		case RELKIND_INDEX:
+			appendStringInfo(buffer, _("index %s"),
+							 relname);
+			break;
+		case RELKIND_SEQUENCE:
+			appendStringInfo(buffer, _("sequence %s"),
+							 relname);
+			break;
+		case RELKIND_TOASTVALUE:
+			appendStringInfo(buffer, _("toast table %s"),
+							 relname);
+			break;
+		case RELKIND_VIEW:
+			appendStringInfo(buffer, _("view %s"),
+							 relname);
+			break;
+		case RELKIND_MATVIEW:
+			appendStringInfo(buffer, _("materialized view %s"),
+							 relname);
+			break;
+		case RELKIND_COMPOSITE_TYPE:
+			appendStringInfo(buffer, _("composite type %s"),
+							 relname);
+			break;
+		case RELKIND_FOREIGN_TABLE:
+			appendStringInfo(buffer, _("foreign table %s"),
+							 relname);
+			break;
+		default:
+			/* shouldn't get here */
+			appendStringInfo(buffer, _("relation %s"),
+							 relname);
+			break;
+	}
+
+	ReleaseSysCache(relTup);
+}
+
+/*
+ * subroutine for getObjectDescription: describe an operator family
+ */
+static void
+getOpFamilyDescription(StringInfo buffer, Oid opfid)
+{
+	HeapTuple	opfTup;
+	Form_pg_opfamily opfForm;
+	HeapTuple	amTup;
+	Form_pg_am	amForm;
+	char	   *nspname;
+
+	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
+	if (!HeapTupleIsValid(opfTup))
+		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
+
+	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
+	if (!HeapTupleIsValid(amTup))
+		elog(ERROR, "cache lookup failed for access method %u",
+			 opfForm->opfmethod);
+	amForm = (Form_pg_am) GETSTRUCT(amTup);
+
+	/* Qualify the name if not visible in search path */
+	if (OpfamilyIsVisible(opfid))
+		nspname = NULL;
+	else
+		nspname = get_namespace_name(opfForm->opfnamespace);
+
+	appendStringInfo(buffer, _("operator family %s for access method %s"),
+					 quote_qualified_identifier(nspname,
+												NameStr(opfForm->opfname)),
+					 NameStr(amForm->amname));
+
+	ReleaseSysCache(amTup);
+	ReleaseSysCache(opfTup);
+}
+
+/*
+ * SQL-level callable version of getObjectDescription
+ */
+Datum
+pg_describe_object(PG_FUNCTION_ARGS)
+{
+	Oid			classid = PG_GETARG_OID(0);
+	Oid			objid = PG_GETARG_OID(1);
+	int32		subobjid = PG_GETARG_INT32(2);
+	char	   *description;
+	ObjectAddress address;
+
+	/* for "pinned" items in pg_depend, return null */
+	if (!OidIsValid(classid) && !OidIsValid(objid))
+		PG_RETURN_NULL();
+
+	address.classId = classid;
+	address.objectId = objid;
+	address.objectSubId = subobjid;
+
+	description = getObjectDescription(&address);
+	PG_RETURN_TEXT_P(cstring_to_text(description));
+}
+
+/*
+ * SQL-level callable function to obtain object type + identity
+ */
+Datum
+pg_identify_object(PG_FUNCTION_ARGS)
+{
+	Oid			classid = PG_GETARG_OID(0);
+	Oid			objid = PG_GETARG_OID(1);
+	int32		subobjid = PG_GETARG_INT32(2);
+	Oid			schema_oid = InvalidOid;
+	const char *objname = NULL;
+	ObjectAddress address;
+	Datum		values[4];
+	bool		nulls[4];
+	TupleDesc	tupdesc;
+	HeapTuple	htup;
+
+	address.classId = classid;
+	address.objectId = objid;
+	address.objectSubId = subobjid;
+
+	/*
+	 * Construct a tuple descriptor for the result row.  This must match this
+	 * function's pg_proc entry!
+	 */
+	tupdesc = CreateTemplateTupleDesc(4, false);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "type",
+					   TEXTOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "schema",
+					   TEXTOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "name",
+					   TEXTOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "identity",
+					   TEXTOID, -1, 0);
+
+	tupdesc = BlessTupleDesc(tupdesc);
+
+	if (is_objectclass_supported(address.classId))
+	{
+		HeapTuple	objtup;
+		Relation	catalog = heap_open(address.classId, AccessShareLock);
+
+		objtup = get_catalog_object_by_oid(catalog, address.objectId);
+		if (objtup != NULL)
+		{
+			bool		isnull;
+			AttrNumber	nspAttnum;
+			AttrNumber	nameAttnum;
+
+			nspAttnum = get_object_attnum_namespace(address.classId);
+			if (nspAttnum != InvalidAttrNumber)
+			{
+				schema_oid = heap_getattr(objtup, nspAttnum,
+										  RelationGetDescr(catalog), &isnull);
+				if (isnull)
+					elog(ERROR, "invalid null namespace in object %u/%u/%d",
+						 address.classId, address.objectId, address.objectSubId);
+			}
+
+			/*
+			 * We only return the object name if it can be used (together
+			 * with the schema name, if any) as an unique identifier.
+			 */
+			if (get_object_namensp_unique(address.classId))
+			{
+				nameAttnum = get_object_attnum_name(address.classId);
+				if (nameAttnum != InvalidAttrNumber)
+				{
+					Datum	nameDatum;
+
+					nameDatum = heap_getattr(objtup, nameAttnum,
+											 RelationGetDescr(catalog), &isnull);
+					if (isnull)
+						elog(ERROR, "invalid null name in object %u/%u/%d",
+							 address.classId, address.objectId, address.objectSubId);
+					objname = quote_identifier(NameStr(*(DatumGetName(nameDatum))));
+				}
+			}
+		}
+
+		heap_close(catalog, AccessShareLock);
+	}
+
+	/* object type */
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	nulls[0] = false;
+
+	/* schema name */
+	if (OidIsValid(schema_oid))
+	{
+		const char	*schema = quote_identifier(get_namespace_name(schema_oid));
+
+		values[1] = CStringGetTextDatum(schema);
+		nulls[1] = false;
+	}
+	else
+		nulls[1] = true;
+
+	/* object name */
+	if (objname)
+	{
+		values[2] = CStringGetTextDatum(objname);
+		nulls[2] = false;
+	}
+	else
+		nulls[2] = true;
+
+	/* object identity */
+	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
+	nulls[3] = false;
+
+	htup = heap_form_tuple(tupdesc, values, nulls);
+
+	PG_RETURN_DATUM(HeapTupleGetDatum(htup));
+}
+
+/*
+ * Return a palloc'ed string that describes the type of object that the
+ * passed address is for.
+ */
+char *
+getObjectTypeDescription(const ObjectAddress *object)
+{
+	StringInfoData buffer;
+
+	initStringInfo(&buffer);
+
+	switch (getObjectClass(object))
+	{
+		case OCLASS_CLASS:
+			getRelationTypeDescription(&buffer, object->objectId,
+									   object->objectSubId);
+			break;
+
+		case OCLASS_PROC:
+			getProcedureTypeDescription(&buffer, object->objectId);
+			break;
+
+		case OCLASS_TYPE:
+			appendStringInfo(&buffer, "type");
+			break;
+
+		case OCLASS_CAST:
+			appendStringInfo(&buffer, "cast");
+			break;
+
+		case OCLASS_COLLATION:
+			appendStringInfo(&buffer, "collation");
+			break;
+
+		case OCLASS_CONSTRAINT:
+			getConstraintTypeDescription(&buffer, object->objectId);
+			break;
+
+		case OCLASS_CONVERSION:
+			appendStringInfo(&buffer, "conversion");
+			break;
+
+		case OCLASS_DEFAULT:
+			appendStringInfo(&buffer, "default value");
+			break;
+
+		case OCLASS_LANGUAGE:
+			appendStringInfo(&buffer, "language");
+			break;
+
+		case OCLASS_LARGEOBJECT:
+			appendStringInfo(&buffer, "large object");
+			break;
+
+		case OCLASS_OPERATOR:
+			appendStringInfo(&buffer, "operator");
+			break;
+
+		case OCLASS_OPCLASS:
+			appendStringInfo(&buffer, "operator class");
+			break;
+
+		case OCLASS_OPFAMILY:
+			appendStringInfo(&buffer, "operator family");
+			break;
+
+		case OCLASS_AMOP:
+			appendStringInfo(&buffer, "operator of access method");
+			break;
+
+		case OCLASS_AMPROC:
+			appendStringInfo(&buffer, "function of access method");
+			break;
+
+		case OCLASS_REWRITE:
+			appendStringInfo(&buffer, "rule");
+			break;
+
+		case OCLASS_TRIGGER:
+			appendStringInfo(&buffer, "trigger");
+			break;
+
+		case OCLASS_SCHEMA:
+			appendStringInfo(&buffer, "schema");
+			break;
+
+		case OCLASS_TSPARSER:
+			appendStringInfo(&buffer, "text search parser");
+			break;
+
+		case OCLASS_TSDICT:
+			appendStringInfo(&buffer, "text search dictionary");
+			break;
+
+		case OCLASS_TSTEMPLATE:
+			appendStringInfo(&buffer, "text search template");
+			break;
+
+		case OCLASS_TSCONFIG:
+			appendStringInfo(&buffer, "text search configuration");
+			break;
+
+		case OCLASS_ROLE:
+			appendStringInfo(&buffer, "role");
+			break;
+
+		case OCLASS_DATABASE:
+			appendStringInfo(&buffer, "database");
+			break;
+
+		case OCLASS_TBLSPACE:
+			appendStringInfo(&buffer, "tablespace");
+			break;
+
+		case OCLASS_FDW:
+			appendStringInfo(&buffer, "foreign-data wrapper");
+			break;
+
+		case OCLASS_FOREIGN_SERVER:
+			appendStringInfo(&buffer, "server");
+			break;
+
+		case OCLASS_USER_MAPPING:
+			appendStringInfo(&buffer, "user mapping");
+			break;
+
+		case OCLASS_DEFACL:
+			appendStringInfo(&buffer, "default acl");
+			break;
+
+		case OCLASS_EXTENSION:
+			appendStringInfo(&buffer, "extension");
+			break;
+
+		case OCLASS_EVENT_TRIGGER:
+			appendStringInfo(&buffer, "event trigger");
+			break;
+
+		default:
+			appendStringInfo(&buffer, "unrecognized %u", object->classId);
+			break;
+	}
+
+	return buffer.data;
+}
+
+/*
+ * subroutine for getObjectTypeDescription: describe a relation type
+ */
+static void
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+{
+	HeapTuple	relTup;
+	Form_pg_class relForm;
+
+	relTup = SearchSysCache1(RELOID,
+							 ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(relTup))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+	relForm = (Form_pg_class) GETSTRUCT(relTup);
+
+	switch (relForm->relkind)
+	{
+		case RELKIND_RELATION:
+			appendStringInfo(buffer, "table");
+			break;
+		case RELKIND_INDEX:
+			appendStringInfo(buffer, "index");
+			break;
+		case RELKIND_SEQUENCE:
+			appendStringInfo(buffer, "sequence");
+			break;
+		case RELKIND_TOASTVALUE:
+			appendStringInfo(buffer, "toast table");
+			break;
+		case RELKIND_VIEW:
+			appendStringInfo(buffer, "view");
+			break;
+		case RELKIND_MATVIEW:
+			appendStringInfo(buffer, "materialized view");
+			break;
+		case RELKIND_COMPOSITE_TYPE:
+			appendStringInfo(buffer, "composite type");
+			break;
+		case RELKIND_FOREIGN_TABLE:
+			appendStringInfo(buffer, "foreign table");
+			break;
+		default:
+			/* shouldn't get here */
+			appendStringInfo(buffer, "relation");
+			break;
+	}
+
+	if (objectSubId != 0)
+		appendStringInfo(buffer, " column");
+
+	ReleaseSysCache(relTup);
+}
+
+/*
+ * subroutine for getObjectTypeDescription: describe a constraint type
+ */
+static void
+getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+{
+	Relation	constrRel;
+	HeapTuple	constrTup;
+	Form_pg_constraint	constrForm;
+
+	constrRel = heap_open(ConstraintRelationId, AccessShareLock);
+	constrTup = get_catalog_object_by_oid(constrRel, constroid);
+	if (!HeapTupleIsValid(constrTup))
+		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
+
+	if (OidIsValid(constrForm->conrelid))
+		appendStringInfoString(buffer, "table constraint");
+	else if (OidIsValid(constrForm->contypid))
+		appendStringInfoString(buffer, "domain constraint");
+	else
+		elog(ERROR, "invalid constraint %u", HeapTupleGetOid(constrTup));
+
+	heap_close(constrRel, AccessShareLock);
+}
+
+/*
+ * subroutine for getObjectTypeDescription: describe a procedure type
+ */
+static void
+getProcedureTypeDescription(StringInfo buffer, Oid procid)
+{
+	HeapTuple	procTup;
+	Form_pg_proc procForm;
+
+	procTup = SearchSysCache1(PROCOID,
+							 ObjectIdGetDatum(procid));
+	if (!HeapTupleIsValid(procTup))
+		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+	if (procForm->proisagg)
+		appendStringInfo(buffer, "aggregate");
+	else
+		appendStringInfo(buffer, "function");
+
+	ReleaseSysCache(procTup);
+}
+
+/*
+ * Return a palloc'ed string that identifies an object.
+ *
+ * This is for machine consumption, so it's not translated.  All elements are
+ * schema-qualified when appropriate.
+ */
+char *
+getObjectIdentity(const ObjectAddress *object)
+{
+	StringInfoData buffer;
+
+	initStringInfo(&buffer);
+
+	switch (getObjectClass(object))
+	{
+		case OCLASS_CLASS:
+			getRelationIdentity(&buffer, object->objectId);
+			if (object->objectSubId != 0)
+			{
+				char   *attr;
+
+				attr = get_relid_attribute_name(object->objectId,
+												object->objectSubId);
+				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
+			}
+			break;
+
+		case OCLASS_PROC:
+			appendStringInfo(&buffer, "%s",
+							 format_procedure_qualified(object->objectId));
+			break;
+
+		case OCLASS_TYPE:
+			appendStringInfo(&buffer, "%s",
+							 format_type_be_qualified(object->objectId));
+			break;
+
+		case OCLASS_CAST:
+			{
+				Relation	castRel;
+				HeapTuple	tup;
+				Form_pg_cast castForm;
+
+				castRel = heap_open(CastRelationId, AccessShareLock);
+
+				tup = get_catalog_object_by_oid(castRel, object->objectId);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for cast %u",
+						 object->objectId);
+
+				castForm = (Form_pg_cast) GETSTRUCT(tup);
+
+				appendStringInfo(&buffer, "(%s AS %s)",
+								 format_type_be_qualified(castForm->castsource),
+								 format_type_be_qualified(castForm->casttarget));
+
+				heap_close(castRel, AccessShareLock);
+				break;
+			}
+
+		case OCLASS_COLLATION:
+			{
+				HeapTuple	collTup;
+				Form_pg_collation coll;
+				char   *schema;
+
+				collTup = SearchSysCache1(COLLOID,
+										  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(collTup))
+					elog(ERROR, "cache lookup failed for collation %u",
+						 object->objectId);
+				coll = (Form_pg_collation) GETSTRUCT(collTup);
+				schema = get_namespace_name(coll->collnamespace);
+				appendStringInfoString(&buffer,
+									   quote_qualified_identifier(schema,
+																  NameStr(coll->collname)));
+				ReleaseSysCache(collTup);
+				break;
+			}
+
+		case OCLASS_CONSTRAINT:
+			{
+				HeapTuple	conTup;
+				Form_pg_constraint con;
+
+				conTup = SearchSysCache1(CONSTROID,
+										 ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(conTup))
+					elog(ERROR, "cache lookup failed for constraint %u",
+						 object->objectId);
+				con = (Form_pg_constraint) GETSTRUCT(conTup);
+
+				if (OidIsValid(con->conrelid))
+				{
+					appendStringInfo(&buffer, "%s on ",
+									 quote_identifier(NameStr(con->conname)));
+					getRelationIdentity(&buffer, con->conrelid);
+				}
+				else
+				{
+					ObjectAddress	domain;
+
+					domain.classId = TypeRelationId;
+					domain.objectId = con->contypid;
+					domain.objectSubId = 0;
+
+					appendStringInfo(&buffer, "%s on %s",
+									 quote_identifier(NameStr(con->conname)),
+									 getObjectIdentity(&domain));
+				}
+
+				ReleaseSysCache(conTup);
+				break;
+			}
+
+		case OCLASS_CONVERSION:
+			{
+				HeapTuple	conTup;
+				Form_pg_conversion conForm;
+
+				conTup = SearchSysCache1(CONVOID,
+										 ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(conTup))
+					elog(ERROR, "cache lookup failed for conversion %u",
+						 object->objectId);
+				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
+				appendStringInfo(&buffer, "%s",
+								 quote_identifier(NameStr(conForm->conname)));
+				ReleaseSysCache(conTup);
+				break;
+			}
+
+		case OCLASS_DEFAULT:
+			{
+				Relation	attrdefDesc;
+				ScanKeyData skey[1];
+				SysScanDesc adscan;
+
+				HeapTuple	tup;
+				Form_pg_attrdef attrdef;
+				ObjectAddress colobject;
+
+				attrdefDesc = heap_open(AttrDefaultRelationId, AccessShareLock);
+
+				ScanKeyInit(&skey[0],
+							ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				adscan = systable_beginscan(attrdefDesc, AttrDefaultOidIndexId,
+											true, SnapshotNow, 1, skey);
+
+				tup = systable_getnext(adscan);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for attrdef %u",
+						 object->objectId);
+
+				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+
+				colobject.classId = RelationRelationId;
+				colobject.objectId = attrdef->adrelid;
+				colobject.objectSubId = attrdef->adnum;
+
+				appendStringInfo(&buffer, "for %s",
+								 getObjectIdentity(&colobject));
+
+				systable_endscan(adscan);
+				heap_close(attrdefDesc, AccessShareLock);
+				break;
+			}
+
+		case OCLASS_LANGUAGE:
+			{
+				HeapTuple	langTup;
+				Form_pg_language langForm;
+
+				langTup = SearchSysCache1(LANGOID,
+										  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(langTup))
+					elog(ERROR, "cache lookup failed for language %u",
+						 object->objectId);
+				langForm = (Form_pg_language) GETSTRUCT(langTup);
+				appendStringInfo(&buffer, "%s",
+								 quote_identifier(NameStr(langForm->lanname)));
+				ReleaseSysCache(langTup);
+				break;
+			}
+		case OCLASS_LARGEOBJECT:
+			appendStringInfo(&buffer, "%u",
+							 object->objectId);
+			break;
+
+		case OCLASS_OPERATOR:
+			appendStringInfo(&buffer, "%s",
+							 format_operator_qualified(object->objectId));
+			break;
+
+		case OCLASS_OPCLASS:
+			{
+				HeapTuple	opcTup;
+				Form_pg_opclass opcForm;
+				HeapTuple	amTup;
+				Form_pg_am	amForm;
+				char	   *schema;
+
+				opcTup = SearchSysCache1(CLAOID,
+										 ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(opcTup))
+					elog(ERROR, "cache lookup failed for opclass %u",
+						 object->objectId);
+				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
+				schema = get_namespace_name(opcForm->opcnamespace);
+
+				amTup = SearchSysCache1(AMOID,
+										ObjectIdGetDatum(opcForm->opcmethod));
+				if (!HeapTupleIsValid(amTup))
+					elog(ERROR, "cache lookup failed for access method %u",
+						 opcForm->opcmethod);
+				amForm = (Form_pg_am) GETSTRUCT(amTup);
+
+				appendStringInfo(&buffer,
+								 "%s",
+								 quote_qualified_identifier(schema,
+															NameStr(opcForm->opcname)));
+				appendStringInfo(&buffer, " for %s",
+								 quote_identifier(NameStr(amForm->amname)));
+
+				ReleaseSysCache(amTup);
+				ReleaseSysCache(opcTup);
+				break;
+			}
+
+		case OCLASS_OPFAMILY:
+			getOpFamilyIdentity(&buffer, object->objectId);
+			break;
+
+		case OCLASS_AMOP:
+			{
+				Relation	amopDesc;
+				HeapTuple	tup;
+				ScanKeyData skey[1];
+				SysScanDesc amscan;
+				Form_pg_amop amopForm;
+				StringInfoData opfam;
+
+				amopDesc = heap_open(AccessMethodOperatorRelationId,
+									 AccessShareLock);
+
+				ScanKeyInit(&skey[0],
+							ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				amscan = systable_beginscan(amopDesc, AccessMethodOperatorOidIndexId, true,
+											SnapshotNow, 1, skey);
+
+				tup = systable_getnext(amscan);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for amop entry %u",
+						 object->objectId);
+
+				amopForm = (Form_pg_amop) GETSTRUCT(tup);
+
+				initStringInfo(&opfam);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily);
+
+				appendStringInfo(&buffer, "operator %d (%s, %s) of %s",
+								 amopForm->amopstrategy,
+								 format_type_be_qualified(amopForm->amoplefttype),
+								 format_type_be_qualified(amopForm->amoprighttype),
+								 opfam.data);
+
+				pfree(opfam.data);
+
+				systable_endscan(amscan);
+				heap_close(amopDesc, AccessShareLock);
+				break;
+			}
+
+		case OCLASS_AMPROC:
+			{
+				Relation	amprocDesc;
+				ScanKeyData skey[1];
+				SysScanDesc amscan;
+				HeapTuple	tup;
+				Form_pg_amproc amprocForm;
+				StringInfoData opfam;
+
+				amprocDesc = heap_open(AccessMethodProcedureRelationId,
+									   AccessShareLock);
+
+				ScanKeyInit(&skey[0],
+							ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				amscan = systable_beginscan(amprocDesc, AccessMethodProcedureOidIndexId, true,
+											SnapshotNow, 1, skey);
+
+				tup = systable_getnext(amscan);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for amproc entry %u",
+						 object->objectId);
+
+				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
+
+				initStringInfo(&opfam);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily);
+
+				appendStringInfo(&buffer, "function %d (%s, %s) of %s",
+								 amprocForm->amprocnum,
+								 format_type_be_qualified(amprocForm->amproclefttype),
+								 format_type_be_qualified(amprocForm->amprocrighttype),
+								 opfam.data);
+
+				pfree(opfam.data);
+
+				systable_endscan(amscan);
+				heap_close(amprocDesc, AccessShareLock);
+				break;
+			}
+
+		case OCLASS_REWRITE:
+			{
+				Relation	ruleDesc;
+				HeapTuple	tup;
+				Form_pg_rewrite rule;
+
+				ruleDesc = heap_open(RewriteRelationId, AccessShareLock);
+
+				tup = get_catalog_object_by_oid(ruleDesc, object->objectId);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for rule %u",
+						 object->objectId);
+
+				rule = (Form_pg_rewrite) GETSTRUCT(tup);
+
+				appendStringInfo(&buffer, "%s on ",
+								 quote_identifier(NameStr(rule->rulename)));
+				getRelationIdentity(&buffer, rule->ev_class);
+
+				heap_close(ruleDesc, AccessShareLock);
+				break;
+			}
+
+		case OCLASS_TRIGGER:
+			{
+				Relation	trigDesc;
+				HeapTuple	tup;
+				Form_pg_trigger trig;
+
+				trigDesc = heap_open(TriggerRelationId, AccessShareLock);
+
+				tup = get_catalog_object_by_oid(trigDesc, object->objectId);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for trigger %u",
+						 object->objectId);
+
+				trig = (Form_pg_trigger) GETSTRUCT(tup);
+
+				appendStringInfo(&buffer, "%s on ",
+								 quote_identifier(NameStr(trig->tgname)));
+				getRelationIdentity(&buffer, trig->tgrelid);
+
+				heap_close(trigDesc, AccessShareLock);
+				break;
+			}
+
+		case OCLASS_SCHEMA:
+			{
+				char	   *nspname;
+
+				nspname = get_namespace_name(object->objectId);
+				if (!nspname)
+					elog(ERROR, "cache lookup failed for namespace %u",
+						 object->objectId);
+				appendStringInfo(&buffer, "%s",
+								 quote_identifier(nspname));
+				break;
+			}
+
+		case OCLASS_TSPARSER:
+			{
+				HeapTuple	tup;
+				Form_pg_ts_parser	formParser;
+
+				tup = SearchSysCache1(TSPARSEROID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for text search parser %u",
+						 object->objectId);
+				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
+				appendStringInfo(&buffer, "%s",
+								 quote_identifier(NameStr(formParser->prsname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_TSDICT:
+			{
+				HeapTuple	tup;
+				Form_pg_ts_dict		formDict;
+
+				tup = SearchSysCache1(TSDICTOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for text search dictionary %u",
+						 object->objectId);
+				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
+				appendStringInfo(&buffer, "%s",
+								 quote_identifier(NameStr(formDict->dictname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_TSTEMPLATE:
+			{
+				HeapTuple	tup;
+				Form_pg_ts_template formTmpl;
+
+				tup = SearchSysCache1(TSTEMPLATEOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for text search template %u",
+						 object->objectId);
+				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
+				appendStringInfo(&buffer, "%s",
+								 quote_identifier(NameStr(formTmpl->tmplname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_TSCONFIG:
+			{
+				HeapTuple	tup;
+				Form_pg_ts_config formCfg;
+
+				tup = SearchSysCache1(TSCONFIGOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for text search configuration %u",
+						 object->objectId);
+				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
+				appendStringInfo(&buffer, "%s",
+								 quote_identifier(NameStr(formCfg->cfgname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_ROLE:
+			{
+				char   *username;
+
+				username = GetUserNameFromId(object->objectId);
+				appendStringInfo(&buffer, "%s",
+								 quote_identifier(username));
+				break;
+			}
+
+		case OCLASS_DATABASE:
+			{
+				char	   *datname;
+
+				datname = get_database_name(object->objectId);
+				if (!datname)
+					elog(ERROR, "cache lookup failed for database %u",
+						 object->objectId);
+				appendStringInfo(&buffer, "%s",
+								 quote_identifier(datname));
+				break;
+			}
+
+		case OCLASS_TBLSPACE:
+			{
+				char	   *tblspace;
+
+				tblspace = get_tablespace_name(object->objectId);
+				if (!tblspace)
+					elog(ERROR, "cache lookup failed for tablespace %u",
+						 object->objectId);
+				appendStringInfo(&buffer, "%s",
+								 quote_identifier(tblspace));
+				break;
+			}
+
+		case OCLASS_FDW:
+			{
+				ForeignDataWrapper *fdw;
+
+				fdw = GetForeignDataWrapper(object->objectId);
+				appendStringInfo(&buffer, "%s",
+								 quote_identifier(fdw->fdwname));
+				break;
+			}
+
+		case OCLASS_FOREIGN_SERVER:
+			{
+				ForeignServer *srv;
+
+				srv = GetForeignServer(object->objectId);
+				appendStringInfo(&buffer, "%s",
+								 quote_identifier(srv->servername));
+				break;
+			}
+
+		case OCLASS_USER_MAPPING:
+			{
+				HeapTuple	tup;
+				Oid			useid;
+				const char *usename;
+
+				tup = SearchSysCache1(USERMAPPINGOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for user mapping %u",
+						 object->objectId);
+
+				useid = ((Form_pg_user_mapping) GETSTRUCT(tup))->umuser;
+
+				ReleaseSysCache(tup);
+
+				if (OidIsValid(useid))
+					usename = quote_identifier(GetUserNameFromId(useid));
+				else
+					usename = "public";
+
+				appendStringInfo(&buffer, "%s", usename);
+				break;
+			}
+
+		case OCLASS_DEFACL:
+			{
+				Relation	defaclrel;
+				ScanKeyData skey[1];
+				SysScanDesc rcscan;
+
+				HeapTuple	tup;
+				Form_pg_default_acl defacl;
+
+				defaclrel = heap_open(DefaultAclRelationId, AccessShareLock);
+
+				ScanKeyInit(&skey[0],
+							ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				rcscan = systable_beginscan(defaclrel, DefaultAclOidIndexId,
+											true, SnapshotNow, 1, skey);
+
+				tup = systable_getnext(rcscan);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for default ACL %u",
+						 object->objectId);
+
+				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
+
+				appendStringInfo(&buffer,
+								 "for role %s",
+								 quote_identifier(GetUserNameFromId(defacl->defaclrole)));
+
+				if (OidIsValid(defacl->defaclnamespace))
+				{
+					char   *schema;
+
+					schema = get_namespace_name(defacl->defaclnamespace);
+					appendStringInfo(&buffer,
+									 " in schema %s",
+									 quote_identifier(schema));
+				}
+
+				switch (defacl->defaclobjtype)
+				{
+					case DEFACLOBJ_RELATION:
+						appendStringInfoString(&buffer,
+											   " on tables");
+						break;
+					case DEFACLOBJ_SEQUENCE:
+						appendStringInfoString(&buffer,
+											   " on sequences");
+						break;
+					case DEFACLOBJ_FUNCTION:
+						appendStringInfoString(&buffer,
+											   " on functions");
+						break;
+					case DEFACLOBJ_TYPE:
+						appendStringInfoString(&buffer,
+											   " on types");
+						break;
+				}
+
+				systable_endscan(rcscan);
+				heap_close(defaclrel, AccessShareLock);
+				break;
+			}
+
+		case OCLASS_EXTENSION:
+			{
+				char	   *extname;
+
+				extname = get_extension_name(object->objectId);
+				if (!extname)
+					elog(ERROR, "cache lookup failed for extension %u",
+						 object->objectId);
+				appendStringInfo(&buffer, "%s",
+								 quote_identifier(extname));
+				break;
+			}
+
+		case OCLASS_EVENT_TRIGGER:
+			{
+				HeapTuple	tup;
+				Form_pg_event_trigger trigForm;
+
+				tup = SearchSysCache1(EVENTTRIGGEROID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for event trigger %u",
+						 object->objectId);
+				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
+				appendStringInfo(&buffer, "%s",
+								 quote_identifier(NameStr(trigForm->evtname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		default:
+			appendStringInfo(&buffer, "unrecognized object %u %u %d",
+							 object->classId,
+							 object->objectId,
+							 object->objectSubId);
+			break;
+	}
+
+	return buffer.data;
+}
+
+static void
+getOpFamilyIdentity(StringInfo buffer, Oid opfid)
+{
+	HeapTuple	opfTup;
+	Form_pg_opfamily opfForm;
+	HeapTuple	amTup;
+	Form_pg_am	amForm;
+	char	   *schema;
+
+	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
+	if (!HeapTupleIsValid(opfTup))
+		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
+
+	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
+	if (!HeapTupleIsValid(amTup))
+		elog(ERROR, "cache lookup failed for access method %u",
+			 opfForm->opfmethod);
+	amForm = (Form_pg_am) GETSTRUCT(amTup);
+
+	schema = get_namespace_name(opfForm->opfnamespace);
+	appendStringInfo(buffer, "%s for %s",
+					 quote_qualified_identifier(schema,
+												NameStr(opfForm->opfname)),
+					 NameStr(amForm->amname));
+
+	ReleaseSysCache(amTup);
+	ReleaseSysCache(opfTup);
+}
+
+/*
+ * Append the relation identity (quoted qualified name) to the given
+ * StringInfo.
+ */
+static void
+getRelationIdentity(StringInfo buffer, Oid relid)
+{
+	HeapTuple	relTup;
+	Form_pg_class relForm;
+	char	   *schema;
+
+	relTup = SearchSysCache1(RELOID,
+							 ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(relTup))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+	relForm = (Form_pg_class) GETSTRUCT(relTup);
+
+	schema = get_namespace_name(relForm->relnamespace);
+	appendStringInfo(buffer, "%s",
+					 quote_qualified_identifier(schema,
+												NameStr(relForm->relname)));
+
+	ReleaseSysCache(relTup);
+}
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 2d2ab1bcfb148079757e7789853388fb6228f44e..665b3804d57e3b79afbf44f2094a7194a9648135 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -752,58 +752,6 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 	}
 }
 
-/*
- * Return a copy of the tuple for the object with the given object OID, from
- * the given catalog (which must have been opened by the caller and suitably
- * locked).  NULL is returned if the OID is not found.
- *
- * We try a syscache first, if available.
- *
- * XXX this function seems general in possible usage.  Given sufficient callers
- * elsewhere, we should consider moving it to a more appropriate place.
- */
-static HeapTuple
-get_catalog_object_by_oid(Relation catalog, Oid objectId)
-{
-	HeapTuple	tuple;
-	Oid			classId = RelationGetRelid(catalog);
-	int			oidCacheId = get_object_catcache_oid(classId);
-
-	if (oidCacheId > 0)
-	{
-		tuple = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objectId));
-		if (!HeapTupleIsValid(tuple))  /* should not happen */
-			return NULL;
-	}
-	else
-	{
-		Oid			oidIndexId = get_object_oid_index(classId);
-		SysScanDesc	scan;
-		ScanKeyData	skey;
-
-		Assert(OidIsValid(oidIndexId));
-
-		ScanKeyInit(&skey,
-					ObjectIdAttributeNumber,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(objectId));
-
-		scan = systable_beginscan(catalog, oidIndexId, true,
-								  SnapshotNow, 1, &skey);
-		tuple = systable_getnext(scan);
-		if (!HeapTupleIsValid(tuple))
-		{
-			systable_endscan(scan);
-			return NULL;
-		}
-		tuple = heap_copytuple(tuple);
-
-		systable_endscan(scan);
-	}
-
-	return tuple;
-}
-
 /*
  * Generic function to change the ownership of a given object, for simple
  * cases (won't work for tables, nor other cases where we need to do more than
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d07f30906c4bfeaa7625e6c0a3e54f343bf9d02..0d82141fef6cb9ae697ae0833febff15bf9236c9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5195,20 +5195,25 @@ opt_restart_seqs:
  *	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 |
- *				   COLLATION | CONVERSION | LANGUAGE | OPERATOR CLASS |
- *				   LARGE OBJECT | CAST | COLUMN | SCHEMA | TABLESPACE |
- *				   EXTENSION | ROLE | TEXT SEARCH PARSER |
- *				   TEXT SEARCH DICTIONARY | TEXT SEARCH TEMPLATE |
- *				   TEXT SEARCH CONFIGURATION | FOREIGN TABLE |
- *				   FOREIGN DATA WRAPPER | SERVER | EVENT TRIGGER |
- *				   MATERIALIZED VIEW] <objname> |
+ *	COMMENT ON [ [ CONVERSION | COLLATION | DATABASE | DOMAIN |
+ *                 EXTENSION | EVENT TRIGGER | FOREIGN DATA WRAPPER |
+ *                 FOREIGN TABLE | INDEX | [PROCEDURAL] LANGUAGE |
+ *                 MATERIALIZED VIEW | ROLE | SCHEMA | SEQUENCE |
+ *                 SERVER | TABLE | TABLESPACE |
+ *                 TEXT SEARCH CONFIGURATION | TEXT SEARCH DICTIONARY |
+ *                 TEXT SEARCH PARSER | TEXT SEARCH TEMPLATE | TYPE |
+ *                 VIEW] <objname> |
  *				 AGGREGATE <aggname> (arg1, ...) |
+ *				 CAST (<src type> AS <dst type>) |
+ *				 COLUMN <relname>.<colname> |
+ *				 CONSTRAINT <constraintname> ON <relname> |
  *				 FUNCTION <funcname> (arg1, arg2, ...) |
+ *				 LARGE OBJECT <oid> |
  *				 OPERATOR <op> (leftoperand_typ, rightoperand_typ) |
- *				 TRIGGER <triggername> ON <relname> |
- *				 CONSTRAINT <constraintname> ON <relname> |
- *				 RULE <rulename> ON <relname> ]
+ *				 OPERATOR CLASS <name> USING <access-method> |
+ *				 OPERATOR FAMILY <name> USING <access-method> |
+ *				 RULE <rulename> ON <relname> |
+ *				 TRIGGER <triggername> ON <relname> ]
  *			   IS 'text'
  *
  *****************************************************************************/
@@ -5332,38 +5337,6 @@ CommentStmt:
 					n->comment = $7;
 					$$ = (Node *) n;
 				}
-			| COMMENT ON TEXT_P SEARCH PARSER any_name IS comment_text
-				{
-					CommentStmt *n = makeNode(CommentStmt);
-					n->objtype = OBJECT_TSPARSER;
-					n->objname = $6;
-					n->comment = $8;
-					$$ = (Node *) n;
-				}
-			| COMMENT ON TEXT_P SEARCH DICTIONARY any_name IS comment_text
-				{
-					CommentStmt *n = makeNode(CommentStmt);
-					n->objtype = OBJECT_TSDICTIONARY;
-					n->objname = $6;
-					n->comment = $8;
-					$$ = (Node *) n;
-				}
-			| COMMENT ON TEXT_P SEARCH TEMPLATE any_name IS comment_text
-				{
-					CommentStmt *n = makeNode(CommentStmt);
-					n->objtype = OBJECT_TSTEMPLATE;
-					n->objname = $6;
-					n->comment = $8;
-					$$ = (Node *) n;
-				}
-			| COMMENT ON TEXT_P SEARCH CONFIGURATION any_name IS comment_text
-				{
-					CommentStmt *n = makeNode(CommentStmt);
-					n->objtype = OBJECT_TSCONFIGURATION;
-					n->objname = $6;
-					n->comment = $8;
-					$$ = (Node *) n;
-				}
 		;
 
 comment_type:
@@ -5386,6 +5359,10 @@ comment_type:
 			| SERVER							{ $$ = OBJECT_FOREIGN_SERVER; }
 			| FOREIGN DATA_P WRAPPER			{ $$ = OBJECT_FDW; }
 			| EVENT TRIGGER						{ $$ = OBJECT_EVENT_TRIGGER; }
+			| TEXT_P SEARCH CONFIGURATION		{ $$ = OBJECT_TSCONFIGURATION; }
+			| TEXT_P SEARCH DICTIONARY			{ $$ = OBJECT_TSDICTIONARY; }
+			| TEXT_P SEARCH PARSER				{ $$ = OBJECT_TSPARSER; }
+			| TEXT_P SEARCH TEMPLATE			{ $$ = OBJECT_TSTEMPLATE; }
 		;
 
 comment_text:
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 99771ec53c09b7e1916321eec6f548a55e4cf572..cd164c7e7ea7b7267db5cf8c4fde774a2dfd37a8 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -29,7 +29,8 @@
 #define MAX_INT32_LEN 11
 
 static char *format_type_internal(Oid type_oid, int32 typemod,
-					 bool typemod_given, bool allow_invalid);
+					 bool typemod_given, bool allow_invalid,
+					 bool force_qualify);
 static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
 static char *
 psnprintf(size_t len, const char *fmt,...)
@@ -77,11 +78,11 @@ format_type(PG_FUNCTION_ARGS)
 	type_oid = PG_GETARG_OID(0);
 
 	if (PG_ARGISNULL(1))
-		result = format_type_internal(type_oid, -1, false, true);
+		result = format_type_internal(type_oid, -1, false, true, false);
 	else
 	{
 		typemod = PG_GETARG_INT32(1);
-		result = format_type_internal(type_oid, typemod, true, true);
+		result = format_type_internal(type_oid, typemod, true, true, false);
 	}
 
 	PG_RETURN_TEXT_P(cstring_to_text(result));
@@ -96,7 +97,13 @@ format_type(PG_FUNCTION_ARGS)
 char *
 format_type_be(Oid type_oid)
 {
-	return format_type_internal(type_oid, -1, false, false);
+	return format_type_internal(type_oid, -1, false, false, false);
+}
+
+char *
+format_type_be_qualified(Oid type_oid)
+{
+	return format_type_internal(type_oid, -1, false, false, true);
 }
 
 /*
@@ -105,14 +112,13 @@ format_type_be(Oid type_oid)
 char *
 format_type_with_typemod(Oid type_oid, int32 typemod)
 {
-	return format_type_internal(type_oid, typemod, true, false);
+	return format_type_internal(type_oid, typemod, true, false, false);
 }
 
-
-
 static char *
 format_type_internal(Oid type_oid, int32 typemod,
-					 bool typemod_given, bool allow_invalid)
+					 bool typemod_given, bool allow_invalid,
+					 bool force_qualify)
 {
 	bool		with_typemod = typemod_given && (typemod >= 0);
 	HeapTuple	tuple;
@@ -300,7 +306,7 @@ format_type_internal(Oid type_oid, int32 typemod,
 		char	   *nspname;
 		char	   *typname;
 
-		if (TypeIsVisible(type_oid))
+		if (!force_qualify && TypeIsVisible(type_oid))
 			nspname = NULL;
 		else
 			nspname = get_namespace_name(typeform->typnamespace);
@@ -421,7 +427,7 @@ oidvectortypes(PG_FUNCTION_ARGS)
 	for (num = 0; num < numargs; num++)
 	{
 		char	   *typename = format_type_internal(oidArray->values[num], -1,
-													false, true);
+													false, true, false);
 		size_t		slen = strlen(typename);
 
 		if (left < (slen + 2))
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index cb11bd0ade63adf8a203a59fc16cf296c0549060..94599aa44ba73367c3660f38ad30f648bcd0028d 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -41,6 +41,8 @@
 #include "utils/syscache.h"
 #include "utils/tqual.h"
 
+static char *format_operator_internal(Oid operator_oid, bool force_qualify);
+static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 					 List **names, int *nargs, Oid *argtypes);
 
@@ -303,6 +305,25 @@ regprocedurein(PG_FUNCTION_ARGS)
  */
 char *
 format_procedure(Oid procedure_oid)
+{
+	return format_procedure_internal(procedure_oid, false);
+}
+
+char *
+format_procedure_qualified(Oid procedure_oid)
+{
+	return format_procedure_internal(procedure_oid, true);
+}
+
+/*
+ * Routine to produce regprocedure names; see format_procedure above.
+ *
+ * force_qualify says whether to schema-qualify; if true, the name is always
+ * qualified regardless of search_path visibility.  Otherwise the name is only
+ * qualified if the function is not in path.
+ */
+static char *
+format_procedure_internal(Oid procedure_oid, bool force_qualify)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -326,7 +347,7 @@ format_procedure(Oid procedure_oid)
 		 * Would this proc be found (given the right args) by regprocedurein?
 		 * If not, we need to qualify it.
 		 */
-		if (FunctionIsVisible(procedure_oid))
+		if (!force_qualify && FunctionIsVisible(procedure_oid))
 			nspname = NULL;
 		else
 			nspname = get_namespace_name(procform->pronamespace);
@@ -339,7 +360,10 @@ format_procedure(Oid procedure_oid)
 
 			if (i > 0)
 				appendStringInfoChar(&buf, ',');
-			appendStringInfoString(&buf, format_type_be(thisargtype));
+			appendStringInfoString(&buf,
+								   force_qualify ?
+								   format_type_be_qualified(thisargtype) :
+								   format_type_be(thisargtype));
 		}
 		appendStringInfoChar(&buf, ')');
 
@@ -653,8 +677,8 @@ regoperatorin(PG_FUNCTION_ARGS)
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
  */
-char *
-format_operator(Oid operator_oid)
+static char *
+format_operator_internal(Oid operator_oid, bool force_qualify)
 {
 	char	   *result;
 	HeapTuple	opertup;
@@ -674,9 +698,9 @@ format_operator(Oid operator_oid)
 
 		/*
 		 * Would this oper be found (given the right args) by regoperatorin?
-		 * If not, we need to qualify it.
+		 * If not, or if caller explicitely requests it, we need to qualify it.
 		 */
-		if (!OperatorIsVisible(operator_oid))
+		if (force_qualify || !OperatorIsVisible(operator_oid))
 		{
 			nspname = get_namespace_name(operform->oprnamespace);
 			appendStringInfo(&buf, "%s.",
@@ -687,12 +711,16 @@ format_operator(Oid operator_oid)
 
 		if (operform->oprleft)
 			appendStringInfo(&buf, "%s,",
+							 force_qualify ?
+							 format_type_be_qualified(operform->oprleft) :
 							 format_type_be(operform->oprleft));
 		else
 			appendStringInfo(&buf, "NONE,");
 
 		if (operform->oprright)
 			appendStringInfo(&buf, "%s)",
+							 force_qualify ?
+							 format_type_be_qualified(operform->oprright) :
 							 format_type_be(operform->oprright));
 		else
 			appendStringInfo(&buf, "NONE)");
@@ -713,6 +741,18 @@ format_operator(Oid operator_oid)
 	return result;
 }
 
+char *
+format_operator(Oid operator_oid)
+{
+	return format_operator_internal(operator_oid, false);
+}
+
+char *
+format_operator_qualified(Oid operator_oid)
+{
+	return format_operator_internal(operator_oid, true);
+}
+
 /*
  * regoperatorout		- converts operator OID to "opr_name(args)"
  */
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index db0776e24d15377607ccab0cf65dd12c8bd94a8a..cbc4673d1b497db7c7488eb8f6ddda38f2b521fa 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201303141
+#define CATALOG_VERSION_NO	201303201
 
 #endif
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8e0837f7d617fbf0bd59adfabeb0bd64ec4f64f7..3aefbb5e6a9066170f493007ee966fa6485c264e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -176,9 +176,6 @@ extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
-extern char *getObjectDescription(const ObjectAddress *object);
-extern char *getObjectDescriptionOids(Oid classid, Oid objid);
-
 extern ObjectAddresses *new_object_addresses(void);
 
 extern void add_exact_object_address(const ObjectAddress *object,
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index ffaf4ea25a132a027baa250937085a69790c2779..2f8f58da9bce3f1e3e29d63e41ea0bab7662d9d2 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -38,6 +38,7 @@ extern void check_object_ownership(Oid roleid,
 
 extern Oid	get_object_namespace(const ObjectAddress *address);
 
+extern bool				is_objectclass_supported(Oid class_id);
 extern Oid				get_object_oid_index(Oid class_id);
 extern int				get_object_catcache_oid(Oid class_id);
 extern int				get_object_catcache_name(Oid class_id);
@@ -46,5 +47,15 @@ extern AttrNumber		get_object_attnum_namespace(Oid class_id);
 extern AttrNumber		get_object_attnum_owner(Oid class_id);
 extern AttrNumber		get_object_attnum_acl(Oid class_id);
 extern AclObjectKind	get_object_aclkind(Oid class_id);
+extern bool				get_object_namensp_unique(Oid class_id);
 
-#endif   /* PARSE_OBJECT_H */
+extern HeapTuple		get_catalog_object_by_oid(Relation catalog,
+						  Oid objectId);
+
+extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescriptionOids(Oid classid, Oid objid);
+
+extern char *getObjectTypeDescription(const ObjectAddress *object);
+extern char *getObjectIdentity(const ObjectAddress *address);
+
+#endif   /* OBJECTADDRESS_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index c97056e167399207c3278ca6bf72d01290f888ab..4aee00233a9b06adea1976f107fb20d4e1a64de2 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2917,6 +2917,9 @@ DESCR("view members of a multixactid");
 DATA(insert OID = 3537 (  pg_describe_object		PGNSP PGUID 12 1 0 0 0 f f f f t f s 3 0 25 "26 26 23" _null_ _null_ _null_ _null_ pg_describe_object _null_ _null_ _null_ ));
 DESCR("get identification of SQL object");
 
+DATA(insert OID = 3839 (  pg_identify_object		PGNSP PGUID 12 1 0 0 0 f f f f t f s 3 0 2249 "26 26 23" "{26,23,23,25,25,25,25}" "{i,i,i,o,o,o,o}" "{classid,objid,subobjid,type,schema,name,identity}" _null_ pg_identify_object _null_ _null_ _null_ ));
+DESCR("get machine-parseable identification of SQL object");
+
 DATA(insert OID = 2079 (  pg_table_is_visible		PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_ pg_table_is_visible _null_ _null_ _null_ ));
 DESCR("is table visible in search path?");
 DATA(insert OID = 2080 (  pg_type_is_visible		PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_ pg_type_is_visible _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index c0debe400c472a5638f68278535dfef2d9fd1547..cd8ac9462b53ef3f9c4bd9fe7469ad24a11f8e13 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -615,7 +615,9 @@ extern Datum regdictionarysend(PG_FUNCTION_ARGS);
 extern Datum text_regclass(PG_FUNCTION_ARGS);
 extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
+extern char *format_procedure_qualified(Oid procedure_oid);
 extern char *format_operator(Oid operator_oid);
+extern char *format_operator_qualified(Oid operator_oid);
 
 /* rowtypes.c */
 extern Datum record_in(PG_FUNCTION_ARGS);
@@ -1027,6 +1029,7 @@ extern Datum pg_encoding_max_length_sql(PG_FUNCTION_ARGS);
 /* format_type.c */
 extern Datum format_type(PG_FUNCTION_ARGS);
 extern char *format_type_be(Oid type_oid);
+extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
 extern Datum oidvectortypes(PG_FUNCTION_ARGS);
 extern int32 type_maximum_size(Oid type_oid, int32 typemod);
@@ -1143,6 +1146,7 @@ extern Datum pg_get_multixact_members(PG_FUNCTION_ARGS);
 
 /* catalogs/dependency.c */
 extern Datum pg_describe_object(PG_FUNCTION_ARGS);
+extern Datum pg_identify_object(PG_FUNCTION_ARGS);
 
 /* commands/constraint.c */
 extern Datum unique_key_recheck(PG_FUNCTION_ARGS);