machine-parseable object descriptions

Started by Alvaro Herreraalmost 13 years ago13 messages
#1Alvaro Herrera
alvherre@2ndquadrant.com

For Dimitri's patch to add support for dropped objects in event
triggers, there's an open question about how to report objects that are
being dropped in a tabular format. What I proposed last had three
columns: (type, schema, identity). The "type" is a description of the
object class; I propose the following list:

table
view
materialized view
foreign table
sequence
...
function
aggregate
type
cast
collation
table constraint
domain constraint
default value
...
operator of access method
function of access method
...
table column
view column
materialized view column
...
default acl for relations
default acl for sequences
default acl for functions
default acl for types

Ellipses hide uninteresting examples. Opinions on this please?

After the object type we have the schema, which would be NULL for
objects that don't belong to schemas (extensions, access methods, casts,
languages, etc).

Then we have the identity, which is a texualt representation of the
other stuff needed to uniquely identify the object being referred to.
For most stuff it's just the name, but for other things it is a bit more
complex. For example, for a function it'd be the function signature;
for a table (and for most other object classes) it's the table name.
For columns, it's the qualified column name i.e.
<tablename>.<columnname>. Note the schema name never appears here, even
if the object is not in path; the whole point of this is to be
machine-parseable and we have the schema separately anyway. So some
tweaks to functions such as format_type_internal are necessary, to
supress schema-qualifying when not wanted.

So the main thing here is about the particular format for each specific
object class. For example, it seems to me we need to schema-qualify
type names in function signatures. For casts I propose "(%s as %s)",
where each %s is a schema-qualified type name. For constraints we can
use "%s on %s" where there's no schema qualification (the schema column
carries the table's schema, for the constraint must always belong to the
same schema). For operators, we use "%s(%s, %s)" where the first %s is
the operator name and the other two are schema-qualified type names.
For default values (pg_attrdef tuples) we use "for %s" where the %s is
the column's identity (i.e. <tabname>.<colname>; again not
schema-qualified because the attrdef "is" in the same schema as the
table)). For operator classes and families we use "%s for %s" where the
first one is the opclass/opfam name, and the second is the AM name. For
pg_amop and pg_amproc objects we use "%d (%s, %s) of %s": strategy
number, qualified type names, and AM name. For rules and triggers, "%s
on %s": rule name and relation name (unqualified). For all the rest, we
simply use the unqualified object name.

pg_default_acl objects are the funniest of all, and they end up as
"belonging to role %s in schema %s". I am especially open to
suggestions in this one.

I think this kind of machine-parseable object representation would be
useful for cases other than event triggers, so I am thinking in
committing this part before the pg_dropped_objects patch. I haven't
split it out yet from the event triggers patch; I will be sending a
patch later, in the meantime I welcome comments on the above.

--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#1)
Re: machine-parseable object descriptions

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

For Dimitri's patch to add support for dropped objects in event
triggers, there's an open question about how to report objects that are
being dropped in a tabular format. What I proposed last had three
columns: (type, schema, identity). The "type" is a description of the
object class; I propose the following list:

I'm okay with the proposed type names.

After the object type we have the schema, which would be NULL for
objects that don't belong to schemas (extensions, access methods, casts,
languages, etc).

Then we have the identity, which is a texualt representation of the
other stuff needed to uniquely identify the object being referred to.

I think you should seriously consider dropping the separate "schema"
column and instead plugging the schema in at the appropriate place
in the identity string (which, evidently, is not always going to be
at the front). Otherwise, how will client code know how to assemble the
schema onto the identity to make a usable name? It's also pretty weird
that some of the names appearing in an identity will be fully qualified
but the most important one isn't.

I could also live with keeping the schema column as proposed, if people
think it has a use, but letting it be redundant with a schema name
included in the identity string. But it seems like a bad idea to try to
shear schema off of identity.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Dimitri Fontaine
dimitri@2ndQuadrant.fr
In reply to: Alvaro Herrera (#1)
Re: machine-parseable object descriptions

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

For Dimitri's patch to add support for dropped objects in event
triggers, there's an open question about how to report objects that are
being dropped in a tabular format. What I proposed last had three
columns: (type, schema, identity). The "type" is a description of the
object class; I propose the following list:

...

Ellipses hide uninteresting examples. Opinions on this please?

In my view, it needs to be the same thing as the second part of the
command tag used for commands on the given type of objects.

Then we have the identity, which is a texualt representation of the
other stuff needed to uniquely identify the object being referred to.
For most stuff it's just the name, but for other things it is a bit more
complex. For example, for a function it'd be the function signature;
for a table (and for most other object classes) it's the table name.
For columns, it's the qualified column name i.e.
<tablename>.<columnname>. Note the schema name never appears here, even
if the object is not in path; the whole point of this is to be
machine-parseable and we have the schema separately anyway. So some
tweaks to functions such as format_type_internal are necessary, to
supress schema-qualifying when not wanted.

As long as we keep also the name in a separate field, I'm in. Also, that
needs to be properly quoted by the backend (MixedCase, extra dots, etc)
as we just said on an external communication channel.

I think this kind of machine-parseable object representation would be
useful for cases other than event triggers, so I am thinking in
committing this part before the pg_dropped_objects patch. I haven't
split it out yet from the event triggers patch; I will be sending a
patch later, in the meantime I welcome comments on the above.

+1

Maybe that can also set the debate for which information to pass down in
Event Trigger User Functions and how. Still don't want to make that a
datatype so that we expose a single variable per object?

Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Dimitri Fontaine
dimitri@2ndQuadrant.fr
In reply to: Tom Lane (#2)
Re: machine-parseable object descriptions

Tom Lane <tgl@sss.pgh.pa.us> writes:

I could also live with keeping the schema column as proposed, if people
think it has a use, but letting it be redundant with a schema name
included in the identity string. But it seems like a bad idea to try to
shear schema off of identity.

+1

Use case for keeping the extra column: replication to a federated
database where you want to tweak the target schema.

Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Darren Duncan
darren@darrenduncan.net
In reply to: Alvaro Herrera (#1)
Re: machine-parseable object descriptions

On 2013.03.18 1:03 PM, Alvaro Herrera wrote:

For Dimitri's patch to add support for dropped objects in event
triggers, there's an open question about how to report objects that are
being dropped in a tabular format.

Now that JSON is a built-in type with 9.2+, could we not perhaps use that to
represent some things in a more natural manner than a tabular format? JSON is
designed to be machine-parseable, and some objects such as routine definitions
are naturally trees of arbitrary depth. -- Darren Duncan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Dimitri Fontaine (#4)
Re: machine-parseable object descriptions

Dimitri Fontaine wrote:

Tom Lane <tgl@sss.pgh.pa.us> writes:

I could also live with keeping the schema column as proposed, if people
think it has a use, but letting it be redundant with a schema name
included in the identity string. But it seems like a bad idea to try to
shear schema off of identity.

+1

Use case for keeping the extra column: replication to a federated
database where you want to tweak the target schema.

If I understood our IM discussion correctly, you were saying that for
federated database replication you wanted a separate "name" column, from
which you could extract a table name easily; not that you wanted a
separate schema column. Anyway the schema column is also present. We
can easily remove columns before commit, if we decide we don't want
them.

In the attached patch, we have these three columns: a "name" column,
which is the object's bare name; a "schema" column, which is the schema;
and an "identity" column, which is the whole thing, with all the schema
qualifications that apply. There's also the type, of course.

I added the name column because it seems to me that it is genuinely
useful for some use cases. However, there are two problems: first, the
original implementation had a slight bug in the case of column objects
(it displayed the name of the relation, not the name of the column), and
two I was afraid it'd be easily misused. One way to attack both things
at once would to be make it NULL except in the cases where it's a truly
unique identifier (tables, schemas, etc). To avoid this being a
standalone "whitelist" of catalogs (which would get outdated quickly, I
fear), I propose to add a new boolean option in ObjectProperty, which
specifies whether the name can be used as an identifier. I have
implemented it that way in the attached patch.

The new identity column is amazingly verbose on things like pg_amproc entries:

10650 | 1 (pg_catalog.point, pg_catalog.point) of pg_catalog.point_ops for gist: pg_catalog.gist_point_consistent(pg_catalog.internal,pg_catalog.point,integer,pg_catalog.oid,pg_catalog.internal)
10651 | 2 (pg_catalog.point, pg_catalog.point) of pg_catalog.point_ops for gist: pg_catalog.gist_box_union(pg_catalog.internal,pg_catalog.internal)
10652 | 3 (pg_catalog.point, pg_catalog.point) of pg_catalog.point_ops for gist: pg_catalog.gist_point_compress(pg_catalog.internal)
10653 | 4 (pg_catalog.point, pg_catalog.point) of pg_catalog.point_ops for gist: pg_catalog.gist_box_decompress(pg_catalog.internal)
10654 | 5 (pg_catalog.point, pg_catalog.point) of pg_catalog.point_ops for gist: pg_catalog.gist_box_penalty(pg_catalog.internal,pg_catalog.internal,pg_catalog.internal)
10655 | 6 (pg_catalog.point, pg_catalog.point) of pg_catalog.point_ops for gist: pg_catalog.gist_box_picksplit(pg_catalog.internal,pg_catalog.internal)
10656 | 7 (pg_catalog.point, pg_catalog.point) of pg_catalog.point_ops for gist: pg_catalog.gist_box_same(pg_catalog.box,pg_catalog.box,pg_catalog.internal)
10657 | 8 (pg_catalog.point, pg_catalog.point) of pg_catalog.point_ops for gist: pg_catalog.gist_point_distance(pg_catalog.internal,pg_catalog.point,integer,pg_catalog.oid)

Also, note how types with standard-specified names ("integer") are not
qualified (which is the right thing, AFAICT). Here's another interesting
example:

alvherre=# create type public.integer as enum ('uno', 'dos');
CREATE TYPE

alvherre=# select * from pg_identify_object('pg_type'::regclass, 'integer'::regtype, 0);
type | schema | name | identity
------+------------+------+----------
type | pg_catalog | int4 | integer
(1 fila)

alvherre=# select * from pg_identify_object('pg_type'::regclass, 'public.integer'::regtype, 0);
type | schema | name | identity
------+--------+-----------+------------------
type | public | "integer" | public."integer"
(1 fila)

If you create a public.int4 type, there's no confusion either, so it's
all consistent.

Here's another bit of sample output, from pg_depend contents (at the
left there's the referencing object, at the right the referenced
object):

alvherre=# select deptype, refd.*, ref.* from pg_depend, lateral (select * from pg_identify_object(classid, objid, objsubid) ) refd, lateral (select * from pg_identify_object(refclassid, refobjid, refobjsubid)) ref where classid <> 0 and refd.schema <> 'pg_catalog' and ref.schema <> 'information_schema' and refd.schema <> 'pg_toast';
deptype | type | schema | name | identity | type | schema | name | identity
---------+-------------------+--------------+--------------+--------------------------------------------------------------+--------------+--------------+-------------+-------------------------------------------
a | domain constraint | public | | "my constr" on public.mydom | type | public | mydom | public.mydom
i | type | "the schema" | "the table" | "the schema"."the table" | table | "the schema" | "the table" | "the schema"."the table"
i | type | "the schema" | "_the table" | "the schema"."the table"[] | type | "the schema" | "the table" | "the schema"."the table"
i | type | public | qx | public.qx | table | public | qx | public.qx
i | type | public | _qx | public.qx[] | type | public | qx | public.qx
a | table constraint | "the schema" | | "the table_another column_check" on "the schema"."the table" | table column | "the schema" | "the table" | "the schema"."the table"."another column"
n | table constraint | "the schema" | | "the table_another column_check" on "the schema"."the table" | table column | "the schema" | "the table" | "the schema"."the table"."another column"
i | type | public | _integer | public."integer"[] | type | public | "integer" | public."integer"
i | type | public | _int4 | public.int4[] | type | public | int4 | public.int4
(9 filas)

alvherre=# \d "the schema"."the table"
Tabla «the schema.the table»
Columna | Tipo | Modificadores
----------------+---------+---------------
the column | integer |
another column | integer |
Restricciones CHECK:
"the table_another column_check" CHECK ("another column" > 0)

All in all, I'm happy with this and I'm considering committing it as
soon as we agree on the set of columns. I'm mildly on the side of
removing the separate schema column and keeping name, so we'd have
type/name/identity.

--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#6)
1 attachment(s)
Re: machine-parseable object descriptions

.. and here's the patch.

--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

identify-object.patchtext/x-diff; charset=us-asciiDownload
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 67,72 ****
--- 67,73 ----
  #include "commands/trigger.h"
  #include "commands/typecmds.h"
  #include "foreign/foreign.h"
+ #include "funcapi.h"
  #include "miscadmin.h"
  #include "nodes/nodeFuncs.h"
  #include "parser/parsetree.h"
***************
*** 198,203 **** static bool stack_address_present_add_flags(const ObjectAddress *object,
--- 199,210 ----
  								ObjectAddressStack *stack);
  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);
  
  
  /*
***************
*** 2193,2199 **** 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",
  			 object->classId);
  
  	switch (object->classId)
--- 2200,2206 ----
  	/* only pg_class entries can have nonzero objectSubId */
  	if (object->classId != RelationRelationId &&
  		object->objectSubId != 0)
! 		elog(ERROR, "invalid non-zero objectSubId for object class %u",
  			 object->classId);
  
  	switch (object->classId)
***************
*** 3087,3093 **** 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 */
--- 3094,3100 ----
  	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 */
***************
*** 3101,3103 **** pg_describe_object(PG_FUNCTION_ARGS)
--- 3108,4145 ----
  	description = getObjectDescription(&address);
  	PG_RETURN_TEXT_P(cstring_to_text(description));
  }
+ 
+ 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);
+ 			}
+ 
+ 			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;
+ 				Form_pg_amop amopForm;
+ 				StringInfoData opfam;
+ 
+ 				amopDesc = heap_open(AccessMethodOperatorRelationId,
+ 									 AccessShareLock);
+ 
+ 				tup = get_catalog_object_by_oid(amopDesc, object->objectId);
+ 
+ 				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);
+ 
+ 				/* XXX is this too verbose? */
+ 				appendStringInfo(&buffer, "%d (%s, %s) of %s: %s",
+ 								 amopForm->amopstrategy,
+ 								 format_type_be_qualified(amopForm->amoplefttype),
+ 								 format_type_be_qualified(amopForm->amoprighttype),
+ 								 opfam.data,
+ 								 format_operator_qualified(amopForm->amopopr));
+ 
+ 				pfree(opfam.data);
+ 
+ 				heap_close(amopDesc, AccessShareLock);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_AMPROC:
+ 			{
+ 				Relation	amprocDesc;
+ 				HeapTuple	tup;
+ 				Form_pg_amproc amprocForm;
+ 				StringInfoData opfam;
+ 
+ 				amprocDesc = heap_open(AccessMethodProcedureRelationId,
+ 									   AccessShareLock);
+ 
+ 				tup = get_catalog_object_by_oid(amprocDesc, object->objectId);
+ 
+ 				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);
+ 
+ 				/* XXX is this too verbose? */
+ 				appendStringInfo(&buffer, "%d (%s, %s) of %s: %s",
+ 								 amprocForm->amprocnum,
+ 								 format_type_be_qualified(amprocForm->amproclefttype),
+ 								 format_type_be_qualified(amprocForm->amprocrighttype),
+ 								 opfam.data,
+ 								 format_procedure_qualified(amprocForm->amproc));
+ 
+ 				pfree(opfam.data);
+ 
+ 				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);
+ }
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
***************
*** 1345,1350 **** get_object_aclkind(Oid class_id)
--- 1345,1368 ----
  }
  
  /*
+  * 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.
   */
  static ObjectPropertyType *
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
***************
*** 753,810 **** 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
   * change the ownership column of a single catalog entry).
--- 753,758 ----
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 5195,5214 **** 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> |
   *				 AGGREGATE <aggname> (arg1, ...) |
   *				 FUNCTION <funcname> (arg1, arg2, ...) |
   *				 OPERATOR <op> (leftoperand_typ, rightoperand_typ) |
   *				 TRIGGER <triggername> ON <relname> |
-  *				 CONSTRAINT <constraintname> ON <relname> |
-  *				 RULE <rulename> ON <relname> ]
   *			   IS 'text'
   *
   *****************************************************************************/
--- 5195,5219 ----
   *	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 [ [ 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) |
+  *               OPERATOR CLASS <name> USING <access-method> |
+  *               OPERATOR FAMILY <name> USING <access-method> |
+  *				 RULE <rulename> ON <relname> |
   *				 TRIGGER <triggername> ON <relname> |
   *			   IS 'text'
   *
   *****************************************************************************/
*** a/src/backend/utils/adt/format_type.c
--- b/src/backend/utils/adt/format_type.c
***************
*** 29,35 ****
  #define MAX_INT32_LEN 11
  
  static char *format_type_internal(Oid type_oid, int32 typemod,
! 					 bool typemod_given, bool allow_invalid);
  static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
  static char *
  psnprintf(size_t len, const char *fmt,...)
--- 29,36 ----
  #define MAX_INT32_LEN 11
  
  static char *format_type_internal(Oid type_oid, int32 typemod,
! 					 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,87 **** format_type(PG_FUNCTION_ARGS)
  	type_oid = PG_GETARG_OID(0);
  
  	if (PG_ARGISNULL(1))
! 		result = format_type_internal(type_oid, -1, false, true);
  	else
  	{
  		typemod = PG_GETARG_INT32(1);
! 		result = format_type_internal(type_oid, typemod, true, true);
  	}
  
  	PG_RETURN_TEXT_P(cstring_to_text(result));
--- 78,88 ----
  	type_oid = PG_GETARG_OID(0);
  
  	if (PG_ARGISNULL(1))
! 		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, false);
  	}
  
  	PG_RETURN_TEXT_P(cstring_to_text(result));
***************
*** 96,102 **** format_type(PG_FUNCTION_ARGS)
  char *
  format_type_be(Oid type_oid)
  {
! 	return format_type_internal(type_oid, -1, false, false);
  }
  
  /*
--- 97,109 ----
  char *
  format_type_be(Oid type_oid)
  {
! 	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,118 **** 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);
  }
  
- 
- 
  static char *
  format_type_internal(Oid type_oid, int32 typemod,
! 					 bool typemod_given, bool allow_invalid)
  {
  	bool		with_typemod = typemod_given && (typemod >= 0);
  	HeapTuple	tuple;
--- 112,124 ----
  char *
  format_type_with_typemod(Oid type_oid, int32 typemod)
  {
! 	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 force_qualify)
  {
  	bool		with_typemod = typemod_given && (typemod >= 0);
  	HeapTuple	tuple;
***************
*** 300,306 **** format_type_internal(Oid type_oid, int32 typemod,
  		char	   *nspname;
  		char	   *typname;
  
! 		if (TypeIsVisible(type_oid))
  			nspname = NULL;
  		else
  			nspname = get_namespace_name(typeform->typnamespace);
--- 306,312 ----
  		char	   *nspname;
  		char	   *typname;
  
! 		if (!force_qualify && TypeIsVisible(type_oid))
  			nspname = NULL;
  		else
  			nspname = get_namespace_name(typeform->typnamespace);
***************
*** 421,427 **** oidvectortypes(PG_FUNCTION_ARGS)
  	for (num = 0; num < numargs; num++)
  	{
  		char	   *typename = format_type_internal(oidArray->values[num], -1,
! 													false, true);
  		size_t		slen = strlen(typename);
  
  		if (left < (slen + 2))
--- 427,433 ----
  	for (num = 0; num < numargs; num++)
  	{
  		char	   *typename = format_type_internal(oidArray->values[num], -1,
! 													false, true, false);
  		size_t		slen = strlen(typename);
  
  		if (left < (slen + 2))
*** a/src/backend/utils/adt/regproc.c
--- b/src/backend/utils/adt/regproc.c
***************
*** 41,46 ****
--- 41,48 ----
  #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);
  
***************
*** 304,309 **** regprocedurein(PG_FUNCTION_ARGS)
--- 306,330 ----
  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,332 **** 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))
  			nspname = NULL;
  		else
  			nspname = get_namespace_name(procform->pronamespace);
--- 347,353 ----
  		 * Would this proc be found (given the right args) by regprocedurein?
  		 * If not, we need to qualify it.
  		 */
! 		if (!force_qualify && FunctionIsVisible(procedure_oid))
  			nspname = NULL;
  		else
  			nspname = get_namespace_name(procform->pronamespace);
***************
*** 339,345 **** format_procedure(Oid procedure_oid)
  
  			if (i > 0)
  				appendStringInfoChar(&buf, ',');
! 			appendStringInfoString(&buf, format_type_be(thisargtype));
  		}
  		appendStringInfoChar(&buf, ')');
  
--- 360,369 ----
  
  			if (i > 0)
  				appendStringInfoChar(&buf, ',');
! 			appendStringInfoString(&buf,
! 								   force_qualify ?
! 								   format_type_be_qualified(thisargtype) :
! 								   format_type_be(thisargtype));
  		}
  		appendStringInfoChar(&buf, ')');
  
***************
*** 653,660 **** 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)
  {
  	char	   *result;
  	HeapTuple	opertup;
--- 677,684 ----
   * This exports the useful functionality of regoperatorout for use
   * in other backend modules.  The result is a palloc'd string.
   */
! static char *
! format_operator_internal(Oid operator_oid, bool force_qualify)
  {
  	char	   *result;
  	HeapTuple	opertup;
***************
*** 674,682 **** format_operator(Oid operator_oid)
  
  		/*
  		 * Would this oper be found (given the right args) by regoperatorin?
! 		 * If not, we need to qualify it.
  		 */
! 		if (!OperatorIsVisible(operator_oid))
  		{
  			nspname = get_namespace_name(operform->oprnamespace);
  			appendStringInfo(&buf, "%s.",
--- 698,706 ----
  
  		/*
  		 * Would this oper be found (given the right args) by regoperatorin?
! 		 * If not, or if caller explicitely requests it, we need to qualify it.
  		 */
! 		if (force_qualify || !OperatorIsVisible(operator_oid))
  		{
  			nspname = get_namespace_name(operform->oprnamespace);
  			appendStringInfo(&buf, "%s.",
***************
*** 687,698 **** format_operator(Oid operator_oid)
--- 711,726 ----
  
  		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,718 **** format_operator(Oid operator_oid)
--- 741,758 ----
  	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)"
   */
*** a/src/backend/utils/cache/lsyscache.c
--- b/src/backend/utils/cache/lsyscache.c
***************
*** 18,23 ****
--- 18,24 ----
  #include "access/hash.h"
  #include "access/htup_details.h"
  #include "access/nbtree.h"
+ #include "access/sysattr.h"
  #include "bootstrap/bootstrap.h"
  #include "catalog/pg_amop.h"
  #include "catalog/pg_amproc.h"
***************
*** 40,45 ****
--- 41,47 ----
  #include "utils/lsyscache.h"
  #include "utils/rel.h"
  #include "utils/syscache.h"
+ #include "utils/tqual.h"
  #include "utils/typcache.h"
  
  /* Hook for plugins to get control in get_attavgwidth() */
***************
*** 2926,2928 **** get_range_subtype(Oid rangeOid)
--- 2928,2981 ----
  	else
  		return InvalidOid;
  }
+ 
+ /*				------------- GENERIC --------------				 */
+ 
+ /*
+  * 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;
+ }
*** a/src/include/catalog/dependency.h
--- b/src/include/catalog/dependency.h
***************
*** 179,184 **** extern ObjectClass getObjectClass(const ObjectAddress *object);
--- 179,187 ----
  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);
+ 
  extern ObjectAddresses *new_object_addresses(void);
  
  extern void add_exact_object_address(const ObjectAddress *object,
*** a/src/include/catalog/objectaddress.h
--- b/src/include/catalog/objectaddress.h
***************
*** 38,43 **** extern void check_object_ownership(Oid roleid,
--- 38,44 ----
  
  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);
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 2917,2922 **** DESCR("view members of a multixactid");
--- 2917,2925 ----
  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_ ));
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 615,621 **** extern Datum regdictionarysend(PG_FUNCTION_ARGS);
--- 615,623 ----
  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,1032 **** extern Datum pg_encoding_max_length_sql(PG_FUNCTION_ARGS);
--- 1029,1035 ----
  /* 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,1148 **** extern Datum pg_get_multixact_members(PG_FUNCTION_ARGS);
--- 1146,1152 ----
  
  /* 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);
*** a/src/include/utils/lsyscache.h
--- b/src/include/utils/lsyscache.h
***************
*** 16,21 ****
--- 16,22 ----
  #include "access/attnum.h"
  #include "access/htup.h"
  #include "nodes/pg_list.h"
+ #include "utils/relcache.h"
  
  /* Result list element for get_op_btree_interpretation */
  typedef struct OpBtreeInterpretation
***************
*** 152,157 **** extern void free_attstatsslot(Oid atttype,
--- 153,159 ----
  				  float4 *numbers, int nnumbers);
  extern char *get_namespace_name(Oid nspid);
  extern Oid	get_range_subtype(Oid rangeOid);
+ extern HeapTuple get_catalog_object_by_oid(Relation catalog, Oid objectId);
  
  #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
  /* type_is_array_domain accepts both plain arrays and domains over arrays */
#8Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#7)
Re: machine-parseable object descriptions

Alvaro Herrera wrote:

.. and here's the patch.

I forgot an example involving the funniest of all object classes:
default ACLs. Here it is.

alvherre=# create role rol1;
CREATE ROLE
alvherre=# create role rol2;
CREATE ROLE
alvherre=# create schema rol1 authorization rol1;
CREATE SCHEMA
alvherre=# alter default privileges for role rol1, rol2 grant insert on tables to alvherre;
ALTER DEFAULT PRIVILEGES
alvherre=# alter default privileges for role rol1 in schema rol1 grant insert on tables to alvherre;
ALTER DEFAULT PRIVILEGES
alvherre=# select oid,foo.* from pg_default_acl, lateral (select * from pg_identify_object(tableoid, oid, 0)) foo ;
oid | type | schema | name | identity
-------+-------------+--------+------+-------------------------------------
48839 | default acl | | | for role rol1 on tables
48840 | default acl | | | for role rol2 on tables
48844 | default acl | | | for role rol1 in schema rol1 on tables
(4 filas)

--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Dimitri Fontaine
dimitri@2ndQuadrant.fr
In reply to: Alvaro Herrera (#6)
Re: machine-parseable object descriptions

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

All in all, I'm happy with this and I'm considering committing it as
soon as we agree on the set of columns. I'm mildly on the side of
removing the separate schema column and keeping name, so we'd have
type/name/identity.

I would prefer that we keep the schema column, for easier per-schema
processing or filtering. It's way eaiser to provide it in a separate
column than to ask people to parse it back from the identity column.

Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#6)
Re: machine-parseable object descriptions

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

The new identity column is amazingly verbose on things like pg_amproc entries:

10650 | 1 (pg_catalog.point, pg_catalog.point) of pg_catalog.point_ops for gist: pg_catalog.gist_point_consistent(pg_catalog.internal,pg_catalog.point,integer,pg_catalog.oid,pg_catalog.internal)

Uh ... isn't that confusing the *identity* of the pg_amproc entry with
its *content*? I would say that the function reference doesn't belong
there. You do need the rest. I would also suggest that you prepend
the word "function" (or "operator" for pg_amop), so that it reads like
"function 1 (typename, typename) of opfamilyname for amname".

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Tom Lane (#10)
1 attachment(s)
Re: machine-parseable object descriptions

Dimitri Fontaine wrote:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

All in all, I'm happy with this and I'm considering committing it as
soon as we agree on the set of columns. I'm mildly on the side of
removing the separate schema column and keeping name, so we'd have
type/name/identity.

I would prefer that we keep the schema column, for easier per-schema
processing or filtering. It's way eaiser to provide it in a separate
column than to ask people to parse it back from the identity column.

Okay.

Tom Lane wrote:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

The new identity column is amazingly verbose on things like pg_amproc entries:

10650 | 1 (pg_catalog.point, pg_catalog.point) of pg_catalog.point_ops for gist: pg_catalog.gist_point_consistent(pg_catalog.internal,pg_catalog.point,integer,pg_catalog.oid,pg_catalog.internal)

Uh ... isn't that confusing the *identity* of the pg_amproc entry with
its *content*? I would say that the function reference doesn't belong
there. You do need the rest. I would also suggest that you prepend
the word "function" (or "operator" for pg_amop), so that it reads like
"function 1 (typename, typename) of opfamilyname for amname".

Uhm, yeah, fixed.

So here's a final version of the patch. With docs too.

One change I made was to move all the new code from dependency.c into
objectaddress.c. The only reason it was in dependency.c was that
getObjectDescription was there in the first place; but it doesn't seem
to me that it really belongs there. (Back when it was first created,
there was no objectaddress.c at all, and dependency.c was the only user
of it.) If there were no backpatching considerations, I would suggest
we move getObjectDescription() to objectaddress.c as well, but I'm not
sure it's worth the trouble, but I'm not wedded to that if somebody
thinks both things should be kept together.

Finally: it'd be nice to be able to get pg_am identities with these
functions too. Then you could use a simple query to get object
identities + descriptions from pg_description (right now you have to
exclude that catalog specifically, otherwise the query bombs out). But
it'd be a lot of trouble, and since these objects are not really
pluggable, I'm not bothering. We can always add it later if there's
more interesting use for it.

--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

identify-object-2.patchtext/x-diff; charset=us-asciiDownload
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 13931,13936 **** SELECT pg_type_is_visible('myschema.widget'::regtype);
--- 13931,13940 ----
     </indexterm>
  
     <indexterm>
+     <primary>pg_identify_object</primary>
+    </indexterm>
+ 
+    <indexterm>
      <primary>pg_get_constraintdef</primary>
     </indexterm>
  
***************
*** 14030,14035 **** SELECT pg_type_is_visible('myschema.widget'::regtype);
--- 14034,14044 ----
         <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>
         <entry>get definition of a constraint</entry>
***************
*** 14273,14285 **** SELECT pg_type_is_visible('myschema.widget'::regtype);
    </para>
  
    <para>
!    <function>pg_describe_object</function> returns a description of a database
     object specified by catalog OID, object OID and a (possibly zero) sub-object ID.
     This is useful to determine the identity of an object as stored in the
     <structname>pg_depend</structname> catalog.
    </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
     dynamically constructing SQL queries.  The function is declared as
--- 14282,14312 ----
    </para>
  
    <para>
!    <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
     dynamically constructing SQL queries.  The function is declared as
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 2193,2199 **** 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",
  			 object->classId);
  
  	switch (object->classId)
--- 2193,2199 ----
  	/* only pg_class entries can have nonzero objectSubId */
  	if (object->classId != RelationRelationId &&
  		object->objectSubId != 0)
! 		elog(ERROR, "invalid non-zero objectSubId for object class %u",
  			 object->classId);
  
  	switch (object->classId)
***************
*** 2537,2545 **** getObjectDescription(const ObjectAddress *object)
  		case OCLASS_AMOP:
  			{
  				Relation	amopDesc;
  				ScanKeyData skey[1];
  				SysScanDesc amscan;
- 				HeapTuple	tup;
  				Form_pg_amop amopForm;
  				StringInfoData opfam;
  
--- 2537,2545 ----
  		case OCLASS_AMOP:
  			{
  				Relation	amopDesc;
+ 				HeapTuple	tup;
  				ScanKeyData skey[1];
  				SysScanDesc amscan;
  				Form_pg_amop amopForm;
  				StringInfoData opfam;
  
***************
*** 3087,3093 **** 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 */
--- 3087,3093 ----
  	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 */
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
***************
*** 20,27 ****
--- 20,31 ----
  #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,51 ****
--- 50,56 ----
  #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,59 ****
--- 59,65 ----
  #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,93 **** typedef struct
--- 94,103 ----
  	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,107 **** static ObjectPropertyType ObjectProperty[] =
  		InvalidAttrNumber,
  		InvalidAttrNumber,
  		InvalidAttrNumber,
! 		-1
  	},
  	{
  		CollationRelationId,
--- 111,118 ----
  		InvalidAttrNumber,
  		InvalidAttrNumber,
  		InvalidAttrNumber,
! 		-1,
! 		false
  	},
  	{
  		CollationRelationId,
***************
*** 112,118 **** static ObjectPropertyType ObjectProperty[] =
  		Anum_pg_collation_collnamespace,
  		Anum_pg_collation_collowner,
  		InvalidAttrNumber,
! 		ACL_KIND_COLLATION
  	},
  	{
  		ConstraintRelationId,
--- 123,130 ----
  		Anum_pg_collation_collnamespace,
  		Anum_pg_collation_collowner,
  		InvalidAttrNumber,
! 		ACL_KIND_COLLATION,
! 		true
  	},
  	{
  		ConstraintRelationId,
***************
*** 123,129 **** static ObjectPropertyType ObjectProperty[] =
  		Anum_pg_constraint_connamespace,
  		InvalidAttrNumber,
  		InvalidAttrNumber,
! 		-1
  	},
  	{
  		ConversionRelationId,
--- 135,142 ----
  		Anum_pg_constraint_connamespace,
  		InvalidAttrNumber,
  		InvalidAttrNumber,
! 		-1,
! 		false
  	},
  	{
  		ConversionRelationId,
***************
*** 134,140 **** static ObjectPropertyType ObjectProperty[] =
  		Anum_pg_conversion_connamespace,
  		Anum_pg_conversion_conowner,
  		InvalidAttrNumber,
! 		ACL_KIND_CONVERSION
  	},
  	{
  		DatabaseRelationId,
--- 147,154 ----
  		Anum_pg_conversion_connamespace,
  		Anum_pg_conversion_conowner,
  		InvalidAttrNumber,
! 		ACL_KIND_CONVERSION,
! 		true
  	},
  	{
  		DatabaseRelationId,
***************
*** 145,151 **** static ObjectPropertyType ObjectProperty[] =
  		InvalidAttrNumber,
  		Anum_pg_database_datdba,
  		Anum_pg_database_datacl,
! 		ACL_KIND_DATABASE
  	},
  	{
  		ExtensionRelationId,
--- 159,166 ----
  		InvalidAttrNumber,
  		Anum_pg_database_datdba,
  		Anum_pg_database_datacl,
! 		ACL_KIND_DATABASE,
! 		true
  	},
  	{
  		ExtensionRelationId,
***************
*** 156,162 **** static ObjectPropertyType ObjectProperty[] =
  		InvalidAttrNumber,		/* extension doesn't belong to extnamespace */
  		Anum_pg_extension_extowner,
  		InvalidAttrNumber,
! 		ACL_KIND_EXTENSION
  	},
  	{
  		ForeignDataWrapperRelationId,
--- 171,178 ----
  		InvalidAttrNumber,		/* extension doesn't belong to extnamespace */
  		Anum_pg_extension_extowner,
  		InvalidAttrNumber,
! 		ACL_KIND_EXTENSION,
! 		true
  	},
  	{
  		ForeignDataWrapperRelationId,
***************
*** 167,173 **** static ObjectPropertyType ObjectProperty[] =
  		InvalidAttrNumber,
  		Anum_pg_foreign_data_wrapper_fdwowner,
  		Anum_pg_foreign_data_wrapper_fdwacl,
! 		ACL_KIND_FDW
  	},
  	{
  		ForeignServerRelationId,
--- 183,190 ----
  		InvalidAttrNumber,
  		Anum_pg_foreign_data_wrapper_fdwowner,
  		Anum_pg_foreign_data_wrapper_fdwacl,
! 		ACL_KIND_FDW,
! 		true
  	},
  	{
  		ForeignServerRelationId,
***************
*** 178,184 **** static ObjectPropertyType ObjectProperty[] =
  		InvalidAttrNumber,
  		Anum_pg_foreign_server_srvowner,
  		Anum_pg_foreign_server_srvacl,
! 		ACL_KIND_FOREIGN_SERVER
  	},
  	{
  		ProcedureRelationId,
--- 195,202 ----
  		InvalidAttrNumber,
  		Anum_pg_foreign_server_srvowner,
  		Anum_pg_foreign_server_srvacl,
! 		ACL_KIND_FOREIGN_SERVER,
! 		true
  	},
  	{
  		ProcedureRelationId,
***************
*** 189,195 **** static ObjectPropertyType ObjectProperty[] =
  		Anum_pg_proc_pronamespace,
  		Anum_pg_proc_proowner,
  		Anum_pg_proc_proacl,
! 		ACL_KIND_PROC
  	},
  	{
  		LanguageRelationId,
--- 207,214 ----
  		Anum_pg_proc_pronamespace,
  		Anum_pg_proc_proowner,
  		Anum_pg_proc_proacl,
! 		ACL_KIND_PROC,
! 		false
  	},
  	{
  		LanguageRelationId,
***************
*** 200,206 **** static ObjectPropertyType ObjectProperty[] =
  		InvalidAttrNumber,
  		Anum_pg_language_lanowner,
  		Anum_pg_language_lanacl,
! 		ACL_KIND_LANGUAGE
  	},
  	{
  		LargeObjectMetadataRelationId,
--- 219,226 ----
  		InvalidAttrNumber,
  		Anum_pg_language_lanowner,
  		Anum_pg_language_lanacl,
! 		ACL_KIND_LANGUAGE,
! 		true
  	},
  	{
  		LargeObjectMetadataRelationId,
***************
*** 211,217 **** static ObjectPropertyType ObjectProperty[] =
  		InvalidAttrNumber,
  		Anum_pg_largeobject_metadata_lomowner,
  		Anum_pg_largeobject_metadata_lomacl,
! 		ACL_KIND_LARGEOBJECT
  	},
  	{
  		OperatorClassRelationId,
--- 231,238 ----
  		InvalidAttrNumber,
  		Anum_pg_largeobject_metadata_lomowner,
  		Anum_pg_largeobject_metadata_lomacl,
! 		ACL_KIND_LARGEOBJECT,
! 		false
  	},
  	{
  		OperatorClassRelationId,
***************
*** 222,228 **** static ObjectPropertyType ObjectProperty[] =
  		Anum_pg_opclass_opcnamespace,
  		Anum_pg_opclass_opcowner,
  		InvalidAttrNumber,
! 		ACL_KIND_OPCLASS
  	},
  	{
  		OperatorRelationId,
--- 243,250 ----
  		Anum_pg_opclass_opcnamespace,
  		Anum_pg_opclass_opcowner,
  		InvalidAttrNumber,
! 		ACL_KIND_OPCLASS,
! 		true
  	},
  	{
  		OperatorRelationId,
***************
*** 233,239 **** static ObjectPropertyType ObjectProperty[] =
  		Anum_pg_operator_oprnamespace,
  		Anum_pg_operator_oprowner,
  		InvalidAttrNumber,
! 		ACL_KIND_OPER
  	},
  	{
  		OperatorFamilyRelationId,
--- 255,262 ----
  		Anum_pg_operator_oprnamespace,
  		Anum_pg_operator_oprowner,
  		InvalidAttrNumber,
! 		ACL_KIND_OPER,
! 		false
  	},
  	{
  		OperatorFamilyRelationId,
***************
*** 244,250 **** static ObjectPropertyType ObjectProperty[] =
  		Anum_pg_opfamily_opfnamespace,
  		Anum_pg_opfamily_opfowner,
  		InvalidAttrNumber,
! 		ACL_KIND_OPFAMILY
  	},
  	{
  		AuthIdRelationId,
--- 267,274 ----
  		Anum_pg_opfamily_opfnamespace,
  		Anum_pg_opfamily_opfowner,
  		InvalidAttrNumber,
! 		ACL_KIND_OPFAMILY,
! 		true
  	},
  	{
  		AuthIdRelationId,
***************
*** 255,261 **** static ObjectPropertyType ObjectProperty[] =
  		InvalidAttrNumber,
  		InvalidAttrNumber,
  		InvalidAttrNumber,
! 		-1
  	},
  	{
  		RewriteRelationId,
--- 279,286 ----
  		InvalidAttrNumber,
  		InvalidAttrNumber,
  		InvalidAttrNumber,
! 		-1,
! 		true
  	},
  	{
  		RewriteRelationId,
***************
*** 266,272 **** static ObjectPropertyType ObjectProperty[] =
  		InvalidAttrNumber,
  		InvalidAttrNumber,
  		InvalidAttrNumber,
! 		-1
  	},
  	{
  		NamespaceRelationId,
--- 291,298 ----
  		InvalidAttrNumber,
  		InvalidAttrNumber,
  		InvalidAttrNumber,
! 		-1,
! 		false
  	},
  	{
  		NamespaceRelationId,
***************
*** 277,283 **** static ObjectPropertyType ObjectProperty[] =
  		InvalidAttrNumber,
  		Anum_pg_namespace_nspowner,
  		Anum_pg_namespace_nspacl,
! 		ACL_KIND_NAMESPACE
  	},
  	{
  		RelationRelationId,
--- 303,310 ----
  		InvalidAttrNumber,
  		Anum_pg_namespace_nspowner,
  		Anum_pg_namespace_nspacl,
! 		ACL_KIND_NAMESPACE,
! 		true
  	},
  	{
  		RelationRelationId,
***************
*** 288,294 **** static ObjectPropertyType ObjectProperty[] =
  		Anum_pg_class_relnamespace,
  		Anum_pg_class_relowner,
  		Anum_pg_class_relacl,
! 		ACL_KIND_CLASS
  	},
  	{
  		TableSpaceRelationId,
--- 315,322 ----
  		Anum_pg_class_relnamespace,
  		Anum_pg_class_relowner,
  		Anum_pg_class_relacl,
! 		ACL_KIND_CLASS,
! 		true
  	},
  	{
  		TableSpaceRelationId,
***************
*** 299,305 **** static ObjectPropertyType ObjectProperty[] =
  		InvalidAttrNumber,
  		Anum_pg_tablespace_spcowner,
  		Anum_pg_tablespace_spcacl,
! 		ACL_KIND_TABLESPACE
  	},
  	{
  		TriggerRelationId,
--- 327,334 ----
  		InvalidAttrNumber,
  		Anum_pg_tablespace_spcowner,
  		Anum_pg_tablespace_spcacl,
! 		ACL_KIND_TABLESPACE,
! 		true
  	},
  	{
  		TriggerRelationId,
***************
*** 311,316 **** static ObjectPropertyType ObjectProperty[] =
--- 340,346 ----
  		InvalidAttrNumber,
  		InvalidAttrNumber,
  		-1,
+ 		false
  	},
  	{
  		EventTriggerRelationId,
***************
*** 322,327 **** static ObjectPropertyType ObjectProperty[] =
--- 352,358 ----
  		Anum_pg_event_trigger_evtowner,
  		InvalidAttrNumber,
  		ACL_KIND_EVENT_TRIGGER,
+ 		true
  	},
  	{
  		TSConfigRelationId,
***************
*** 332,338 **** static ObjectPropertyType ObjectProperty[] =
  		Anum_pg_ts_config_cfgnamespace,
  		Anum_pg_ts_config_cfgowner,
  		InvalidAttrNumber,
! 		ACL_KIND_TSCONFIGURATION
  	},
  	{
  		TSDictionaryRelationId,
--- 363,370 ----
  		Anum_pg_ts_config_cfgnamespace,
  		Anum_pg_ts_config_cfgowner,
  		InvalidAttrNumber,
! 		ACL_KIND_TSCONFIGURATION,
! 		true
  	},
  	{
  		TSDictionaryRelationId,
***************
*** 343,349 **** static ObjectPropertyType ObjectProperty[] =
  		Anum_pg_ts_dict_dictnamespace,
  		Anum_pg_ts_dict_dictowner,
  		InvalidAttrNumber,
! 		ACL_KIND_TSDICTIONARY
  	},
  	{
  		TSParserRelationId,
--- 375,382 ----
  		Anum_pg_ts_dict_dictnamespace,
  		Anum_pg_ts_dict_dictowner,
  		InvalidAttrNumber,
! 		ACL_KIND_TSDICTIONARY,
! 		true
  	},
  	{
  		TSParserRelationId,
***************
*** 355,360 **** static ObjectPropertyType ObjectProperty[] =
--- 388,394 ----
  		InvalidAttrNumber,
  		InvalidAttrNumber,
  		-1,
+ 		true
  	},
  	{
  		TSTemplateRelationId,
***************
*** 366,371 **** static ObjectPropertyType ObjectProperty[] =
--- 400,406 ----
  		InvalidAttrNumber,
  		InvalidAttrNumber,
  		-1,
+ 		true,
  	},
  	{
  		TypeRelationId,
***************
*** 376,382 **** static ObjectPropertyType ObjectProperty[] =
  		Anum_pg_type_typnamespace,
  		Anum_pg_type_typowner,
  		Anum_pg_type_typacl,
! 		ACL_KIND_TYPE
  	}
  };
  
--- 411,418 ----
  		Anum_pg_type_typnamespace,
  		Anum_pg_type_typowner,
  		Anum_pg_type_typacl,
! 		ACL_KIND_TYPE,
! 		true
  	}
  };
  
***************
*** 395,400 **** static ObjectAddress get_object_address_type(ObjectType objtype,
--- 431,442 ----
  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 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
***************
*** 1344,1349 **** get_object_aclkind(Oid class_id)
--- 1386,1417 ----
  	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,1376 **** get_object_property_data(Oid class_id)
--- 1442,2507 ----
  
  	return NULL; /* keep MSC compiler happy */
  }
+ 
+ /*
+  * 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);
+ }
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
***************
*** 753,810 **** 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
   * change the ownership column of a single catalog entry).
--- 753,758 ----
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 5195,5214 **** 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> |
   *				 AGGREGATE <aggname> (arg1, ...) |
   *				 FUNCTION <funcname> (arg1, arg2, ...) |
   *				 OPERATOR <op> (leftoperand_typ, rightoperand_typ) |
!  *				 TRIGGER <triggername> ON <relname> |
!  *				 CONSTRAINT <constraintname> ON <relname> |
!  *				 RULE <rulename> ON <relname> ]
   *			   IS 'text'
   *
   *****************************************************************************/
--- 5195,5219 ----
   *	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 [ [ 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) |
!  *				 OPERATOR CLASS <name> USING <access-method> |
!  *				 OPERATOR FAMILY <name> USING <access-method> |
!  *				 RULE <rulename> ON <relname> |
!  *				 TRIGGER <triggername> ON <relname> ]
   *			   IS 'text'
   *
   *****************************************************************************/
***************
*** 5332,5369 **** 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:
--- 5337,5342 ----
***************
*** 5386,5391 **** comment_type:
--- 5359,5368 ----
  			| 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:
*** a/src/backend/utils/adt/format_type.c
--- b/src/backend/utils/adt/format_type.c
***************
*** 29,35 ****
  #define MAX_INT32_LEN 11
  
  static char *format_type_internal(Oid type_oid, int32 typemod,
! 					 bool typemod_given, bool allow_invalid);
  static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
  static char *
  psnprintf(size_t len, const char *fmt,...)
--- 29,36 ----
  #define MAX_INT32_LEN 11
  
  static char *format_type_internal(Oid type_oid, int32 typemod,
! 					 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,87 **** format_type(PG_FUNCTION_ARGS)
  	type_oid = PG_GETARG_OID(0);
  
  	if (PG_ARGISNULL(1))
! 		result = format_type_internal(type_oid, -1, false, true);
  	else
  	{
  		typemod = PG_GETARG_INT32(1);
! 		result = format_type_internal(type_oid, typemod, true, true);
  	}
  
  	PG_RETURN_TEXT_P(cstring_to_text(result));
--- 78,88 ----
  	type_oid = PG_GETARG_OID(0);
  
  	if (PG_ARGISNULL(1))
! 		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, false);
  	}
  
  	PG_RETURN_TEXT_P(cstring_to_text(result));
***************
*** 96,102 **** format_type(PG_FUNCTION_ARGS)
  char *
  format_type_be(Oid type_oid)
  {
! 	return format_type_internal(type_oid, -1, false, false);
  }
  
  /*
--- 97,109 ----
  char *
  format_type_be(Oid type_oid)
  {
! 	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,118 **** 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);
  }
  
- 
- 
  static char *
  format_type_internal(Oid type_oid, int32 typemod,
! 					 bool typemod_given, bool allow_invalid)
  {
  	bool		with_typemod = typemod_given && (typemod >= 0);
  	HeapTuple	tuple;
--- 112,124 ----
  char *
  format_type_with_typemod(Oid type_oid, int32 typemod)
  {
! 	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 force_qualify)
  {
  	bool		with_typemod = typemod_given && (typemod >= 0);
  	HeapTuple	tuple;
***************
*** 300,306 **** format_type_internal(Oid type_oid, int32 typemod,
  		char	   *nspname;
  		char	   *typname;
  
! 		if (TypeIsVisible(type_oid))
  			nspname = NULL;
  		else
  			nspname = get_namespace_name(typeform->typnamespace);
--- 306,312 ----
  		char	   *nspname;
  		char	   *typname;
  
! 		if (!force_qualify && TypeIsVisible(type_oid))
  			nspname = NULL;
  		else
  			nspname = get_namespace_name(typeform->typnamespace);
***************
*** 421,427 **** oidvectortypes(PG_FUNCTION_ARGS)
  	for (num = 0; num < numargs; num++)
  	{
  		char	   *typename = format_type_internal(oidArray->values[num], -1,
! 													false, true);
  		size_t		slen = strlen(typename);
  
  		if (left < (slen + 2))
--- 427,433 ----
  	for (num = 0; num < numargs; num++)
  	{
  		char	   *typename = format_type_internal(oidArray->values[num], -1,
! 													false, true, false);
  		size_t		slen = strlen(typename);
  
  		if (left < (slen + 2))
*** a/src/backend/utils/adt/regproc.c
--- b/src/backend/utils/adt/regproc.c
***************
*** 41,46 ****
--- 41,48 ----
  #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);
  
***************
*** 304,309 **** regprocedurein(PG_FUNCTION_ARGS)
--- 306,330 ----
  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,332 **** 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))
  			nspname = NULL;
  		else
  			nspname = get_namespace_name(procform->pronamespace);
--- 347,353 ----
  		 * Would this proc be found (given the right args) by regprocedurein?
  		 * If not, we need to qualify it.
  		 */
! 		if (!force_qualify && FunctionIsVisible(procedure_oid))
  			nspname = NULL;
  		else
  			nspname = get_namespace_name(procform->pronamespace);
***************
*** 339,345 **** format_procedure(Oid procedure_oid)
  
  			if (i > 0)
  				appendStringInfoChar(&buf, ',');
! 			appendStringInfoString(&buf, format_type_be(thisargtype));
  		}
  		appendStringInfoChar(&buf, ')');
  
--- 360,369 ----
  
  			if (i > 0)
  				appendStringInfoChar(&buf, ',');
! 			appendStringInfoString(&buf,
! 								   force_qualify ?
! 								   format_type_be_qualified(thisargtype) :
! 								   format_type_be(thisargtype));
  		}
  		appendStringInfoChar(&buf, ')');
  
***************
*** 653,660 **** 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)
  {
  	char	   *result;
  	HeapTuple	opertup;
--- 677,684 ----
   * This exports the useful functionality of regoperatorout for use
   * in other backend modules.  The result is a palloc'd string.
   */
! static char *
! format_operator_internal(Oid operator_oid, bool force_qualify)
  {
  	char	   *result;
  	HeapTuple	opertup;
***************
*** 674,682 **** format_operator(Oid operator_oid)
  
  		/*
  		 * Would this oper be found (given the right args) by regoperatorin?
! 		 * If not, we need to qualify it.
  		 */
! 		if (!OperatorIsVisible(operator_oid))
  		{
  			nspname = get_namespace_name(operform->oprnamespace);
  			appendStringInfo(&buf, "%s.",
--- 698,706 ----
  
  		/*
  		 * Would this oper be found (given the right args) by regoperatorin?
! 		 * If not, or if caller explicitely requests it, we need to qualify it.
  		 */
! 		if (force_qualify || !OperatorIsVisible(operator_oid))
  		{
  			nspname = get_namespace_name(operform->oprnamespace);
  			appendStringInfo(&buf, "%s.",
***************
*** 687,698 **** format_operator(Oid operator_oid)
--- 711,726 ----
  
  		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,718 **** format_operator(Oid operator_oid)
--- 741,758 ----
  	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)"
   */
*** a/src/backend/utils/cache/lsyscache.c
--- b/src/backend/utils/cache/lsyscache.c
***************
*** 18,23 ****
--- 18,24 ----
  #include "access/hash.h"
  #include "access/htup_details.h"
  #include "access/nbtree.h"
+ #include "access/sysattr.h"
  #include "bootstrap/bootstrap.h"
  #include "catalog/pg_amop.h"
  #include "catalog/pg_amproc.h"
***************
*** 40,45 ****
--- 41,47 ----
  #include "utils/lsyscache.h"
  #include "utils/rel.h"
  #include "utils/syscache.h"
+ #include "utils/tqual.h"
  #include "utils/typcache.h"
  
  /* Hook for plugins to get control in get_attavgwidth() */
***************
*** 2926,2928 **** get_range_subtype(Oid rangeOid)
--- 2928,2981 ----
  	else
  		return InvalidOid;
  }
+ 
+ /*				------------- GENERIC --------------				 */
+ 
+ /*
+  * 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;
+ }
*** a/src/include/catalog/catversion.h
--- b/src/include/catalog/catversion.h
***************
*** 53,58 ****
   */
  
  /*							yyyymmddN */
! #define CATALOG_VERSION_NO	201303141
  
  #endif
--- 53,58 ----
   */
  
  /*							yyyymmddN */
! #define CATALOG_VERSION_NO	201303201
  
  #endif
*** a/src/include/catalog/dependency.h
--- b/src/include/catalog/dependency.h
***************
*** 179,184 **** extern ObjectClass getObjectClass(const ObjectAddress *object);
--- 179,187 ----
  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);
+ 
  extern ObjectAddresses *new_object_addresses(void);
  
  extern void add_exact_object_address(const ObjectAddress *object,
*** a/src/include/catalog/objectaddress.h
--- b/src/include/catalog/objectaddress.h
***************
*** 38,43 **** extern void check_object_ownership(Oid roleid,
--- 38,44 ----
  
  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,50 **** extern AttrNumber		get_object_attnum_namespace(Oid class_id);
--- 47,52 ----
  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 */
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 2917,2922 **** DESCR("view members of a multixactid");
--- 2917,2925 ----
  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_ ));
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 615,621 **** extern Datum regdictionarysend(PG_FUNCTION_ARGS);
--- 615,623 ----
  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,1032 **** extern Datum pg_encoding_max_length_sql(PG_FUNCTION_ARGS);
--- 1029,1035 ----
  /* 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,1148 **** extern Datum pg_get_multixact_members(PG_FUNCTION_ARGS);
--- 1146,1152 ----
  
  /* 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);
*** a/src/include/utils/lsyscache.h
--- b/src/include/utils/lsyscache.h
***************
*** 16,21 ****
--- 16,22 ----
  #include "access/attnum.h"
  #include "access/htup.h"
  #include "nodes/pg_list.h"
+ #include "utils/relcache.h"
  
  /* Result list element for get_op_btree_interpretation */
  typedef struct OpBtreeInterpretation
***************
*** 152,157 **** extern void free_attstatsslot(Oid atttype,
--- 153,159 ----
  				  float4 *numbers, int nnumbers);
  extern char *get_namespace_name(Oid nspid);
  extern Oid	get_range_subtype(Oid rangeOid);
+ extern HeapTuple get_catalog_object_by_oid(Relation catalog, Oid objectId);
  
  #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
  /* type_is_array_domain accepts both plain arrays and domains over arrays */
#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#11)
Re: machine-parseable object descriptions

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

One change I made was to move all the new code from dependency.c into
objectaddress.c. The only reason it was in dependency.c was that
getObjectDescription was there in the first place; but it doesn't seem
to me that it really belongs there. (Back when it was first created,
there was no objectaddress.c at all, and dependency.c was the only user
of it.) If there were no backpatching considerations, I would suggest
we move getObjectDescription() to objectaddress.c as well, but I'm not
sure it's worth the trouble, but I'm not wedded to that if somebody
thinks both things should be kept together.

+1 for moving getObjectDescription to objectaddress.c. As you say,
that's probably where it would've been if that file had existed at
the time. I don't recall that we've had to back-patch many changes
in that function, so I don't think that concern is major.

Finally: it'd be nice to be able to get pg_am identities with these
functions too. Then you could use a simple query to get object
identities + descriptions from pg_description (right now you have to
exclude that catalog specifically, otherwise the query bombs out). But
it'd be a lot of trouble, and since these objects are not really
pluggable, I'm not bothering. We can always add it later if there's
more interesting use for it.

I think that would be a good thing to add, but no objection to leaving
it for a follow-on patch.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Tom Lane (#12)
Re: machine-parseable object descriptions

Tom Lane wrote:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

One change I made was to move all the new code from dependency.c into
objectaddress.c. The only reason it was in dependency.c was that
getObjectDescription was there in the first place; but it doesn't seem
to me that it really belongs there. (Back when it was first created,
there was no objectaddress.c at all, and dependency.c was the only user
of it.) If there were no backpatching considerations, I would suggest
we move getObjectDescription() to objectaddress.c as well, but I'm not
sure it's worth the trouble, but I'm not wedded to that if somebody
thinks both things should be kept together.

+1 for moving getObjectDescription to objectaddress.c. As you say,
that's probably where it would've been if that file had existed at
the time. I don't recall that we've had to back-patch many changes
in that function, so I don't think that concern is major.

Okay, I have pushed it with that change. Thanks for the quick feedback.

--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers