*** a/doc/src/sgml/event-trigger.sgml
--- b/doc/src/sgml/event-trigger.sgml
***************
*** 36,43 ****
       The <literal>ddl_command_start</> event occurs just before the
       execution of a <literal>CREATE</>, <literal>ALTER</>, or <literal>DROP</>
       command.  As an exception, however, this event does not occur for
!      DDL commands targeting shared objects - databases, roles, and tablespaces
!      - or for command targeting event triggers themselves.  The event trigger
       mechanism does not support these object types.
       <literal>ddl_command_start</> also occurs just before the execution of a
       <literal>SELECT INTO</literal> command, since this is equivalent to
--- 36,43 ----
       The <literal>ddl_command_start</> event occurs just before the
       execution of a <literal>CREATE</>, <literal>ALTER</>, or <literal>DROP</>
       command.  As an exception, however, this event does not occur for
!      DDL commands targeting shared objects &mdash; databases, roles, and tablespaces
!      &mdash; or for command targeting event triggers themselves.  The event trigger
       mechanism does not support these object types.
       <literal>ddl_command_start</> also occurs just before the execution of a
       <literal>SELECT INTO</literal> command, since this is equivalent to
***************
*** 46,51 ****
--- 46,61 ----
     </para>
  
     <para>
+     To list all objects that have been deleted as part of executing a
+     command, use the set returning
+     function <literal>pg_event_trigger_dropped_objects()</> from
+     your <literal>ddl_command_end</> event trigger code (see
+     <xref linkend="functions-event-triggers">). Note that
+     the trigger is executed after the objects have been deleted from the
+     system catalogs, so it's not possible to look them up anymore.
+    </para>
+ 
+    <para>
       Event triggers (like other functions) cannot be executed in an aborted
       transaction.  Thus, if a DDL command fails with an error, any associated
       <literal>ddl_command_end</> triggers will not be executed.  Conversely,
***************
*** 433,438 ****
--- 443,453 ----
          <entry align="center"><literal>X</literal></entry>
         </row>
         <row>
+         <entry align="left"><literal>DROP OWNED</literal></entry>
+         <entry align="center"><literal>X</literal></entry>
+         <entry align="center"><literal>X</literal></entry>
+        </row>
+        <row>
          <entry align="left"><literal>DROP RULE</literal></entry>
          <entry align="center"><literal>X</literal></entry>
          <entry align="center"><literal>X</literal></entry>
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 15750,15753 **** FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger();
--- 15750,15803 ----
          <xref linkend="SQL-CREATETRIGGER">.
      </para>
    </sect1>
+ 
+   <sect1 id="functions-event-triggers">
+    <title>Event Trigger Functions</title>
+ 
+    <indexterm>
+      <primary>pg_event_trigger_dropped_objects</primary>
+    </indexterm>
+ 
+    <para>
+     Currently <productname>PostgreSQL</> provides one built-in event trigger
+     helper function, <function>pg_event_trigger_dropped_objects</>, which
+     lists all object dropped by the command in whose <literal>ddl_command_end</>
+     event it is called.  If the function is run in a context other than a
+     <literal>ddl_command_end</> event trigger function, or if it's run in the
+     <literal>ddl_command_end</> event of a command that does not drop objects,
+     it will return the empty set.
+     </para>
+ 
+    <para>
+     The <function>pg_event_trigger_dropped_objects</> function can be used
+     in an event trigger like this:
+ <programlisting>
+ CREATE FUNCTION test_event_trigger_for_drops()
+         RETURNS event_trigger LANGUAGE plpgsql AS $$
+ DECLARE
+     obj record;
+ BEGIN
+     FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
+     LOOP
+         RAISE NOTICE '% dropped object: % %.% %',
+                      tg_tag,
+                      obj.object_type,
+                      obj.schema_name,
+                      obj.object_name,
+                      obj.subobject_name;
+     END LOOP;
+ END
+ $$;
+ CREATE EVENT TRIGGER test_event_trigger_for_drops
+    ON ddl_command_end
+    EXECUTE PROCEDURE test_event_trigger_for_drops();
+ </programlisting>
+     </para>
+ 
+      <para>
+        For more information about event triggers,
+        see <xref linkend="event-triggers">.
+     </para>
+   </sect1>
+ 
  </chapter>
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 198,203 **** static bool stack_address_present_add_flags(const ObjectAddress *object,
--- 198,207 ----
  								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 getOpFamilyIdentity(StringInfo buffer, Oid opfid);
+ static void getRelationIdentity(StringInfo buffer, Oid relid);
  
  
  /*
***************
*** 267,272 **** performDeletion(const ObjectAddress *object,
--- 271,282 ----
  	{
  		ObjectAddress *thisobj = targetObjects->refs + i;
  
+ 		if ((!(flags & PERFORM_DELETION_INTERNAL)) &&
+ 			EventTriggerSupportsObjectType(getObjectClass(thisobj)))
+ 		{
+ 			evtrig_sqldrop_add_object(thisobj);
+ 		}
+ 
  		deleteOneObject(thisobj, &depRel, flags);
  	}
  
***************
*** 349,354 **** performMultipleDeletions(const ObjectAddresses *objects,
--- 359,370 ----
  	{
  		ObjectAddress *thisobj = targetObjects->refs + i;
  
+ 		if ((!(flags & PERFORM_DELETION_INTERNAL)) &&
+ 			EventTriggerSupportsObjectType(getObjectClass(thisobj)))
+ 		{
+ 			evtrig_sqldrop_add_object(thisobj);
+ 		}
+ 
  		deleteOneObject(thisobj, &depRel, flags);
  	}
  
***************
*** 366,371 **** performMultipleDeletions(const ObjectAddresses *objects,
--- 382,391 ----
   * This is currently used only to clean out the contents of a schema
   * (namespace): the passed object is a namespace.  We normally want this
   * to be done silently, so there's an option to suppress NOTICE messages.
+  *
+  * Note we don't fire object drop event triggers here; it would be wrong to do
+  * so for the current only use of this function, but if more callers are added
+  * this might need to be reconsidered.
   */
  void
  deleteWhatDependsOn(const ObjectAddress *object,
***************
*** 3101,3103 **** pg_describe_object(PG_FUNCTION_ARGS)
--- 3121,3991 ----
  	description = getObjectDescription(&address);
  	PG_RETURN_TEXT_P(cstring_to_text(description));
  }
+ 
+ /*
+  * 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:
+ 			appendStringInfo(&buffer, "function");
+ 			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:
+ 			appendStringInfo(&buffer, "constraint");
+ 			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:
+ 			/* FIXME need more detail here, on defaclobjtype */
+ 			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");
+ 			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);
+ }
+ 
+ /*
+  * Return a palloc'ed string that identifies an object.
+  *
+  * This is for machine consumption, so it's not translated; and we assume that
+  * the schema name, when pertinent, is going to be obtained by some other
+  * means, so we don't schema-qualify either.
+  */
+ char *
+ getObjectIdentity(const ObjectAddress *object)
+ {
+ 	StringInfoData buffer;
+ 
+ 	initStringInfo(&buffer);
+ 
+ 	switch (getObjectClass(object))
+ 	{
+ 		case OCLASS_CLASS:
+ 			getRelationIdentity(&buffer, object->objectId);
+ 			if (object->objectSubId != 0)
+ 				appendStringInfo(&buffer, ".%s",
+ 								 get_relid_attribute_name(object->objectId,
+ 													   object->objectSubId));
+ 			break;
+ 
+ 		case OCLASS_PROC:
+ 			appendStringInfo(&buffer, "%s",
+ 							 format_procedure_internal(object->objectId,
+ 													   false));
+ 			break;
+ 
+ 		case OCLASS_TYPE:
+ 			/* FIXME -- avoid schema-qualifying this */
+ 			appendStringInfo(&buffer, "%s",
+ 							 format_type_be(object->objectId));
+ 			break;
+ 
+ 		case OCLASS_CAST:
+ 			{
+ 				Relation	castDesc;
+ 				ScanKeyData skey[1];
+ 				SysScanDesc rcscan;
+ 				HeapTuple	tup;
+ 				Form_pg_cast castForm;
+ 
+ 				castDesc = heap_open(CastRelationId, AccessShareLock);
+ 
+ 				ScanKeyInit(&skey[0],
+ 							ObjectIdAttributeNumber,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(object->objectId));
+ 
+ 				rcscan = systable_beginscan(castDesc, CastOidIndexId, true,
+ 											SnapshotNow, 1, skey);
+ 
+ 				tup = systable_getnext(rcscan);
+ 
+ 				if (!HeapTupleIsValid(tup))
+ 					elog(ERROR, "could not find tuple for cast %u",
+ 						 object->objectId);
+ 
+ 				castForm = (Form_pg_cast) GETSTRUCT(tup);
+ 
+ 				appendStringInfo(&buffer, "(%s AS %s)",
+ 								 format_type_be(castForm->castsource),
+ 								 format_type_be(castForm->casttarget));
+ 
+ 				systable_endscan(rcscan);
+ 				heap_close(castDesc, AccessShareLock);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_COLLATION:
+ 			{
+ 				HeapTuple	collTup;
+ 				Form_pg_collation coll;
+ 
+ 				collTup = SearchSysCache1(COLLOID,
+ 										  ObjectIdGetDatum(object->objectId));
+ 				if (!HeapTupleIsValid(collTup))
+ 					elog(ERROR, "cache lookup failed for collation %u",
+ 						 object->objectId);
+ 				coll = (Form_pg_collation) GETSTRUCT(collTup);
+ 				appendStringInfo(&buffer, "%s",
+ 								 NameStr(coll->collname));
+ 				ReleaseSysCache(collTup);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_CONSTRAINT:
+ 			{
+ 				HeapTuple	conTup;
+ 				Form_pg_constraint con;
+ 
+ 				conTup = SearchSysCache1(CONSTROID,
+ 										 ObjectIdGetDatum(object->objectId));
+ 				if (!HeapTupleIsValid(conTup))
+ 					elog(ERROR, "cache lookup failed for constraint %u",
+ 						 object->objectId);
+ 				con = (Form_pg_constraint) GETSTRUCT(conTup);
+ 
+ 				if (OidIsValid(con->conrelid))
+ 				{
+ 					StringInfoData rel;
+ 
+ 					initStringInfo(&rel);
+ 					getRelationIdentity(&rel, con->conrelid);
+ 					appendStringInfo(&buffer, "%s on %s",
+ 									 NameStr(con->conname), rel.data);
+ 					pfree(rel.data);
+ 				}
+ 				else
+ 				{
+ 					/* FIXME add domain name */
+ 					appendStringInfo(&buffer, "%s",
+ 									 NameStr(con->conname));
+ 				}
+ 
+ 				ReleaseSysCache(conTup);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_CONVERSION:
+ 			{
+ 				HeapTuple	conTup;
+ 
+ 				conTup = SearchSysCache1(CONVOID,
+ 										 ObjectIdGetDatum(object->objectId));
+ 				if (!HeapTupleIsValid(conTup))
+ 					elog(ERROR, "cache lookup failed for conversion %u",
+ 						 object->objectId);
+ 				appendStringInfo(&buffer, "%s",
+ 				 NameStr(((Form_pg_conversion) GETSTRUCT(conTup))->conname));
+ 				ReleaseSysCache(conTup);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_DEFAULT:
+ 			{
+ 				Relation	attrdefDesc;
+ 				ScanKeyData skey[1];
+ 				SysScanDesc adscan;
+ 				HeapTuple	tup;
+ 				Form_pg_attrdef attrdef;
+ 				ObjectAddress colobject;
+ 
+ 				attrdefDesc = heap_open(AttrDefaultRelationId, AccessShareLock);
+ 
+ 				ScanKeyInit(&skey[0],
+ 							ObjectIdAttributeNumber,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(object->objectId));
+ 
+ 				adscan = systable_beginscan(attrdefDesc, AttrDefaultOidIndexId,
+ 											true, SnapshotNow, 1, skey);
+ 
+ 				tup = systable_getnext(adscan);
+ 
+ 				if (!HeapTupleIsValid(tup))
+ 					elog(ERROR, "could not find tuple for attrdef %u",
+ 						 object->objectId);
+ 
+ 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+ 
+ 				colobject.classId = RelationRelationId;
+ 				colobject.objectId = attrdef->adrelid;
+ 				colobject.objectSubId = attrdef->adnum;
+ 
+ 				appendStringInfo(&buffer, "for %s",
+ 								 getObjectIdentity(&colobject));
+ 
+ 				systable_endscan(adscan);
+ 				heap_close(attrdefDesc, AccessShareLock);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_LANGUAGE:
+ 			{
+ 				HeapTuple	langTup;
+ 
+ 				langTup = SearchSysCache1(LANGOID,
+ 										  ObjectIdGetDatum(object->objectId));
+ 				if (!HeapTupleIsValid(langTup))
+ 					elog(ERROR, "cache lookup failed for language %u",
+ 						 object->objectId);
+ 				appendStringInfo(&buffer, "%s",
+ 				  NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname));
+ 				ReleaseSysCache(langTup);
+ 				break;
+ 			}
+ 		case OCLASS_LARGEOBJECT:
+ 			appendStringInfo(&buffer, "%u",
+ 							 object->objectId);
+ 			break;
+ 
+ 		case OCLASS_OPERATOR:
+ 			/* FIXME avoid schema-qualifying this */
+ 			appendStringInfo(&buffer, "%s",
+ 							 format_operator(object->objectId));
+ 			break;
+ 
+ 		case OCLASS_OPCLASS:
+ 			{
+ 				HeapTuple	opcTup;
+ 				Form_pg_opclass opcForm;
+ 				HeapTuple	amTup;
+ 				Form_pg_am	amForm;
+ 
+ 				opcTup = SearchSysCache1(CLAOID,
+ 										 ObjectIdGetDatum(object->objectId));
+ 				if (!HeapTupleIsValid(opcTup))
+ 					elog(ERROR, "cache lookup failed for opclass %u",
+ 						 object->objectId);
+ 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
+ 
+ 				amTup = SearchSysCache1(AMOID,
+ 										ObjectIdGetDatum(opcForm->opcmethod));
+ 				if (!HeapTupleIsValid(amTup))
+ 					elog(ERROR, "cache lookup failed for access method %u",
+ 						 opcForm->opcmethod);
+ 				amForm = (Form_pg_am) GETSTRUCT(amTup);
+ 
+ 				appendStringInfo(&buffer, "%s for %s",
+ 								 NameStr(opcForm->opcname),
+ 								 NameStr(amForm->amname));
+ 
+ 				ReleaseSysCache(amTup);
+ 				ReleaseSysCache(opcTup);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_OPFAMILY:
+ 			getOpFamilyIdentity(&buffer, object->objectId);
+ 			break;
+ 
+ 		case OCLASS_AMOP:
+ 			{
+ 				Relation	amopDesc;
+ 				ScanKeyData skey[1];
+ 				SysScanDesc amscan;
+ 				HeapTuple	tup;
+ 				Form_pg_amop amopForm;
+ 				StringInfoData opfam;
+ 
+ 				amopDesc = heap_open(AccessMethodOperatorRelationId,
+ 									 AccessShareLock);
+ 
+ 				ScanKeyInit(&skey[0],
+ 							ObjectIdAttributeNumber,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(object->objectId));
+ 
+ 				amscan = systable_beginscan(amopDesc, AccessMethodOperatorOidIndexId, true,
+ 											SnapshotNow, 1, skey);
+ 
+ 				tup = systable_getnext(amscan);
+ 
+ 				if (!HeapTupleIsValid(tup))
+ 					elog(ERROR, "could not find tuple for amop entry %u",
+ 						 object->objectId);
+ 
+ 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
+ 
+ 				initStringInfo(&opfam);
+ 				getOpFamilyIdentity(&opfam, amopForm->amopfamily);
+ 
+ 				/* XXX is this too verbose? */
+ 				appendStringInfo(&buffer, "%d (%s, %s) of %s: %s",
+ 								 amopForm->amopstrategy,
+ 								 format_type_be(amopForm->amoplefttype),
+ 								 format_type_be(amopForm->amoprighttype),
+ 								 opfam.data,
+ 								 format_operator(amopForm->amopopr));
+ 
+ 				pfree(opfam.data);
+ 
+ 				systable_endscan(amscan);
+ 				heap_close(amopDesc, AccessShareLock);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_AMPROC:
+ 			{
+ 				Relation	amprocDesc;
+ 				ScanKeyData skey[1];
+ 				SysScanDesc amscan;
+ 				HeapTuple	tup;
+ 				Form_pg_amproc amprocForm;
+ 				StringInfoData opfam;
+ 
+ 				amprocDesc = heap_open(AccessMethodProcedureRelationId,
+ 									   AccessShareLock);
+ 
+ 				ScanKeyInit(&skey[0],
+ 							ObjectIdAttributeNumber,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(object->objectId));
+ 
+ 				amscan = systable_beginscan(amprocDesc, AccessMethodProcedureOidIndexId, true,
+ 											SnapshotNow, 1, skey);
+ 
+ 				tup = systable_getnext(amscan);
+ 
+ 				if (!HeapTupleIsValid(tup))
+ 					elog(ERROR, "could not find tuple for amproc entry %u",
+ 						 object->objectId);
+ 
+ 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
+ 
+ 				initStringInfo(&opfam);
+ 				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily);
+ 
+ 				/* XXX is this too verbose? */
+ 				appendStringInfo(&buffer, "%d (%s, %s) of %s: %s",
+ 								 amprocForm->amprocnum,
+ 								 format_type_be(amprocForm->amproclefttype),
+ 								 format_type_be(amprocForm->amprocrighttype),
+ 								 opfam.data,
+ 								 format_procedure_internal(amprocForm->amproc,
+ 														   false));
+ 
+ 				pfree(opfam.data);
+ 
+ 				systable_endscan(amscan);
+ 				heap_close(amprocDesc, AccessShareLock);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_REWRITE:
+ 			{
+ 				Relation	ruleDesc;
+ 				ScanKeyData skey[1];
+ 				SysScanDesc rcscan;
+ 				HeapTuple	tup;
+ 				Form_pg_rewrite rule;
+ 
+ 				ruleDesc = heap_open(RewriteRelationId, AccessShareLock);
+ 
+ 				ScanKeyInit(&skey[0],
+ 							ObjectIdAttributeNumber,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(object->objectId));
+ 
+ 				rcscan = systable_beginscan(ruleDesc, RewriteOidIndexId, true,
+ 											SnapshotNow, 1, skey);
+ 
+ 				tup = systable_getnext(rcscan);
+ 
+ 				if (!HeapTupleIsValid(tup))
+ 					elog(ERROR, "could not find tuple for rule %u",
+ 						 object->objectId);
+ 
+ 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
+ 
+ 				appendStringInfo(&buffer, "%s on ",
+ 								 NameStr(rule->rulename));
+ 				getRelationIdentity(&buffer, rule->ev_class);
+ 
+ 				systable_endscan(rcscan);
+ 				heap_close(ruleDesc, AccessShareLock);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_TRIGGER:
+ 			{
+ 				Relation	trigDesc;
+ 				ScanKeyData skey[1];
+ 				SysScanDesc tgscan;
+ 				HeapTuple	tup;
+ 				Form_pg_trigger trig;
+ 
+ 				trigDesc = heap_open(TriggerRelationId, AccessShareLock);
+ 
+ 				ScanKeyInit(&skey[0],
+ 							ObjectIdAttributeNumber,
+ 							BTEqualStrategyNumber, F_OIDEQ,
+ 							ObjectIdGetDatum(object->objectId));
+ 
+ 				tgscan = systable_beginscan(trigDesc, TriggerOidIndexId, true,
+ 											SnapshotNow, 1, skey);
+ 
+ 				tup = systable_getnext(tgscan);
+ 
+ 				if (!HeapTupleIsValid(tup))
+ 					elog(ERROR, "could not find tuple for trigger %u",
+ 						 object->objectId);
+ 
+ 				trig = (Form_pg_trigger) GETSTRUCT(tup);
+ 
+ 				appendStringInfo(&buffer, "%s on ",
+ 								 NameStr(trig->tgname));
+ 				getRelationIdentity(&buffer, trig->tgrelid);
+ 
+ 				systable_endscan(tgscan);
+ 				heap_close(trigDesc, AccessShareLock);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_SCHEMA:
+ 			{
+ 				char	   *nspname;
+ 
+ 				nspname = get_namespace_name(object->objectId);
+ 				if (!nspname)
+ 					elog(ERROR, "cache lookup failed for namespace %u",
+ 						 object->objectId);
+ 				appendStringInfo(&buffer, "%s", nspname);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_TSPARSER:
+ 			{
+ 				HeapTuple	tup;
+ 
+ 				tup = SearchSysCache1(TSPARSEROID,
+ 									  ObjectIdGetDatum(object->objectId));
+ 				if (!HeapTupleIsValid(tup))
+ 					elog(ERROR, "cache lookup failed for text search parser %u",
+ 						 object->objectId);
+ 				appendStringInfo(&buffer, "%s",
+ 					 NameStr(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname));
+ 				ReleaseSysCache(tup);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_TSDICT:
+ 			{
+ 				HeapTuple	tup;
+ 
+ 				tup = SearchSysCache1(TSDICTOID,
+ 									  ObjectIdGetDatum(object->objectId));
+ 				if (!HeapTupleIsValid(tup))
+ 					elog(ERROR, "cache lookup failed for text search dictionary %u",
+ 						 object->objectId);
+ 				appendStringInfo(&buffer, "%s",
+ 					  NameStr(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname));
+ 				ReleaseSysCache(tup);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_TSTEMPLATE:
+ 			{
+ 				HeapTuple	tup;
+ 
+ 				tup = SearchSysCache1(TSTEMPLATEOID,
+ 									  ObjectIdGetDatum(object->objectId));
+ 				if (!HeapTupleIsValid(tup))
+ 					elog(ERROR, "cache lookup failed for text search template %u",
+ 						 object->objectId);
+ 				appendStringInfo(&buffer, "%s",
+ 				  NameStr(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname));
+ 				ReleaseSysCache(tup);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_TSCONFIG:
+ 			{
+ 				HeapTuple	tup;
+ 
+ 				tup = SearchSysCache1(TSCONFIGOID,
+ 									  ObjectIdGetDatum(object->objectId));
+ 				if (!HeapTupleIsValid(tup))
+ 					elog(ERROR, "cache lookup failed for text search configuration %u",
+ 						 object->objectId);
+ 				appendStringInfo(&buffer, "%s",
+ 					 NameStr(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname));
+ 				ReleaseSysCache(tup);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_ROLE:
+ 			{
+ 				appendStringInfo(&buffer, "%s",
+ 								 GetUserNameFromId(object->objectId));
+ 				break;
+ 			}
+ 
+ 		case OCLASS_DATABASE:
+ 			{
+ 				char	   *datname;
+ 
+ 				datname = get_database_name(object->objectId);
+ 				if (!datname)
+ 					elog(ERROR, "cache lookup failed for database %u",
+ 						 object->objectId);
+ 				appendStringInfo(&buffer, "%s", datname);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_TBLSPACE:
+ 			{
+ 				char	   *tblspace;
+ 
+ 				tblspace = get_tablespace_name(object->objectId);
+ 				if (!tblspace)
+ 					elog(ERROR, "cache lookup failed for tablespace %u",
+ 						 object->objectId);
+ 				appendStringInfo(&buffer, "%s", tblspace);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_FDW:
+ 			{
+ 				ForeignDataWrapper *fdw;
+ 
+ 				fdw = GetForeignDataWrapper(object->objectId);
+ 				appendStringInfo(&buffer, "%s", fdw->fdwname);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_FOREIGN_SERVER:
+ 			{
+ 				ForeignServer *srv;
+ 
+ 				srv = GetForeignServer(object->objectId);
+ 				appendStringInfo(&buffer, "%s", srv->servername);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_USER_MAPPING:
+ 			{
+ 				HeapTuple	tup;
+ 				Oid			useid;
+ 				char	   *usename;
+ 
+ 				tup = SearchSysCache1(USERMAPPINGOID,
+ 									  ObjectIdGetDatum(object->objectId));
+ 				if (!HeapTupleIsValid(tup))
+ 					elog(ERROR, "cache lookup failed for user mapping %u",
+ 						 object->objectId);
+ 
+ 				useid = ((Form_pg_user_mapping) GETSTRUCT(tup))->umuser;
+ 
+ 				ReleaseSysCache(tup);
+ 
+ 				if (OidIsValid(useid))
+ 					usename = GetUserNameFromId(useid);
+ 				else
+ 					usename = "public";
+ 
+ 				appendStringInfo(&buffer, "%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,
+ 								 "belonging to role %s",
+ 								 GetUserNameFromId(defacl->defaclrole));
+ 
+ 				if (OidIsValid(defacl->defaclnamespace))
+ 				{
+ 					appendStringInfo(&buffer,
+ 									 " in schema %s",
+ 								get_namespace_name(defacl->defaclnamespace));
+ 				}
+ 
+ 				systable_endscan(rcscan);
+ 				heap_close(defaclrel, AccessShareLock);
+ 				break;
+ 			}
+ 
+ 		case OCLASS_EXTENSION:
+ 			{
+ 				char	   *extname;
+ 
+ 				extname = get_extension_name(object->objectId);
+ 				if (!extname)
+ 					elog(ERROR, "cache lookup failed for extension %u",
+ 						 object->objectId);
+ 				appendStringInfo(&buffer, "%s", extname);
+ 				break;
+ 			}
+ 
+         case OCLASS_EVENT_TRIGGER:
+ 			{
+ 				HeapTuple	tup;
+ 
+ 				tup = SearchSysCache1(EVENTTRIGGEROID,
+ 									  ObjectIdGetDatum(object->objectId));
+ 				if (!HeapTupleIsValid(tup))
+ 					elog(ERROR, "cache lookup failed for event trigger %u",
+ 						 object->objectId);
+ 				appendStringInfo(&buffer, "%s",
+ 					 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
+ 				ReleaseSysCache(tup);
+ 				break;
+ 			}
+ 
+ 		default:
+ 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
+ 							 object->classId,
+ 							 object->objectId,
+ 							 object->objectSubId);
+ 			break;
+ 	}
+ 
+ 	return buffer.data;
+ }
+ 
+ static void
+ getOpFamilyIdentity(StringInfo buffer, Oid opfid)
+ {
+ 	HeapTuple	opfTup;
+ 	Form_pg_opfamily opfForm;
+ 	HeapTuple	amTup;
+ 	Form_pg_am	amForm;
+ 
+ 	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);
+ 
+ 	appendStringInfo(buffer, "%s for access method %s",
+ 					 NameStr(opfForm->opfname),
+ 					 NameStr(amForm->amname));
+ 
+ 	ReleaseSysCache(amTup);
+ 	ReleaseSysCache(opfTup);
+ }
+ 
+ static void
+ getRelationIdentity(StringInfo buffer, Oid relid)
+ {
+ 	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);
+ 
+ 	appendStringInfo(buffer, "%s",
+ 					 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
***************
*** 748,805 **** 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).
--- 748,753 ----
*** a/src/backend/commands/event_trigger.c
--- b/src/backend/commands/event_trigger.c
***************
*** 19,30 ****
--- 19,32 ----
  #include "catalog/indexing.h"
  #include "catalog/objectaccess.h"
  #include "catalog/pg_event_trigger.h"
+ #include "catalog/pg_namespace.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_trigger.h"
  #include "catalog/pg_type.h"
  #include "commands/dbcommands.h"
  #include "commands/event_trigger.h"
  #include "commands/trigger.h"
+ #include "funcapi.h"
  #include "parser/parse_func.h"
  #include "pgstat.h"
  #include "miscadmin.h"
***************
*** 39,44 ****
--- 41,50 ----
  #include "utils/syscache.h"
  #include "tcop/utility.h"
  
+ /* -- Globally visible state -- */
+ 
+ EventTriggerQueryState	*currentEventTriggerState = NULL;
+ 
  typedef struct
  {
  	const char	   *obtypename;
***************
*** 89,94 **** static event_trigger_support_data event_trigger_support[] = {
--- 95,110 ----
  	{ NULL, false }
  };
  
+ /* Support for dropped objects */
+ typedef struct SQLDropObject
+ {
+ 	ObjectAddress	address;
+ 	char		   *objidentity;
+ 	char		   *schemaname;
+ 	char		   *objecttype;
+ 	slist_node		next;
+ } SQLDropObject;
+ 
  static void AlterEventTriggerOwner_internal(Relation rel,
  											HeapTuple tup,
  											Oid newOwnerId);
***************
*** 151,158 **** CreateEventTrigger(CreateEventTrigStmt *stmt)
  	}
  
  	/* Validate tag list, if any. */
! 	if (strcmp(stmt->eventname, "ddl_command_start") == 0 && tags != NULL)
  		validate_ddl_tags("tag", tags);
  
  	/*
  	 * Give user a nice error message if an event trigger of the same name
--- 167,178 ----
  	}
  
  	/* Validate tag list, if any. */
! 	if ((strcmp(stmt->eventname, "ddl_command_start") == 0 ||
! 		 strcmp(stmt->eventname, "ddl_command_end") == 0)
! 		&& tags != NULL)
! 	{
  		validate_ddl_tags("tag", tags);
+ 	}
  
  	/*
  	 * Give user a nice error message if an event trigger of the same name
***************
*** 220,226 **** check_ddl_tag(const char *tag)
  		pg_strcasecmp(tag, "SELECT INTO") == 0 ||
  		pg_strcasecmp(tag, "REFRESH MATERIALIZED VIEW") == 0 ||
  		pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 ||
! 		pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0)
  		return EVENT_TRIGGER_COMMAND_TAG_OK;
  
  	/*
--- 240,247 ----
  		pg_strcasecmp(tag, "SELECT INTO") == 0 ||
  		pg_strcasecmp(tag, "REFRESH MATERIALIZED VIEW") == 0 ||
  		pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 ||
! 		pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0 ||
! 		pg_strcasecmp(tag, "DROP OWNED") == 0)
  		return EVENT_TRIGGER_COMMAND_TAG_OK;
  
  	/*
***************
*** 826,828 **** EventTriggerSupportsObjectType(ObjectType obtype)
--- 847,1080 ----
  	}
  	return true;
  }
+ 
+ /*
+  * Prepare event trigger for running a new query.
+  */
+ EventTriggerQueryState *
+ EventTriggerBeginCompleteQuery(void)
+ {
+ 	EventTriggerQueryState *prevstate;
+ 	EventTriggerQueryState *state;
+ 	MemoryContext	cxt;
+ 
+ 	prevstate = currentEventTriggerState;
+ 
+ 	cxt = AllocSetContextCreate(TopMemoryContext,
+ 								"event trigger state",
+ 								ALLOCSET_DEFAULT_MINSIZE,
+ 								ALLOCSET_DEFAULT_INITSIZE,
+ 								ALLOCSET_DEFAULT_MAXSIZE);
+ 	state = MemoryContextAlloc(cxt, sizeof(EventTriggerQueryState));
+ 	state->cxt = cxt;
+ 	slist_init(&(state->SQLDropList));
+ 
+ 	currentEventTriggerState = state;
+ 
+ 	return prevstate;
+ }
+ 
+ /*
+  * Query completed (or errored out) -- clean up local state
+  *
+  * XXX we currently don't use "abort" for anything ...
+  */
+ void
+ EventTriggerEndCompleteQuery(EventTriggerQueryState *prevstate, bool abort)
+ {
+ 	/* this avoids the need for retail pfree of SQLDropList items: */
+ 	MemoryContextDelete(currentEventTriggerState->cxt);
+ 
+ 	currentEventTriggerState = prevstate;
+ }
+ 
+ /*
+  * Support for dropped objects information on event trigger functions.
+  *
+  * We keep the list of objects dropped by the current command in current
+  * state's SQLDropList (comprising SQLDropObject items).  Each time a new
+  * command is to start, a clean EventTriggerQueryState is created; commands
+  * that drop objects do the dependency.c dance to drop objects, which
+  * populates the current state's SQLDropList; when the event triggers are
+  * invoked they can consume the list via pg_event_trigger_dropped_objects().
+  * When the command finishes, the EventTriggerQueryState is cleared, and
+  * the one from the previous command is restored (when no command is in
+  * execution, the current state is NULL).
+  *
+  * All this lets us support the case that an event trigger function drops
+  * objects "reentrantly".
+  */
+ 
+ /*
+  * Register one object as being dropped by the current command.
+  */
+ void
+ evtrig_sqldrop_add_object(ObjectAddress *object)
+ {
+ 	SQLDropObject  *obj;
+ 	MemoryContext	oldcxt;
+ 
+ 	if (!currentEventTriggerState)
+ 		return;
+ 
+ 	Assert(EventTriggerSupportsObjectType(getObjectClass(object)));
+ 
+ 	/* don't report temp schemas */
+ 	if (object->classId == NamespaceRelationId &&
+ 		isAnyTempNamespace(object->objectId))
+ 		return;
+ 
+ 	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+ 
+ 	obj = palloc0(sizeof(SQLDropObject));
+ 	obj->address = *object;
+ 
+ 	/*
+ 	 * Obtain schema names from the object's catalog tuple, if one exists;
+ 	 * this lets us skip objects in temp schemas.  We trust that ObjectProperty
+ 	 * contains all object classes that can be schema-qualified.
+ 	 */
+ 	if (is_objectclass_supported(object->classId))
+ 	{
+ 		Relation	catalog;
+ 		HeapTuple	tuple;
+ 
+ 		catalog = heap_open(obj->address.classId, AccessShareLock);
+ 		tuple = get_catalog_object_by_oid(catalog, obj->address.objectId);
+ 
+ 		if (tuple)
+ 		{
+ 			AttrNumber	attnum;
+ 			Datum		datum;
+ 			bool		isnull;
+ 
+ 			attnum = get_object_attnum_namespace(obj->address.classId);
+ 			if (attnum != InvalidAttrNumber)
+ 			{
+ 				datum = heap_getattr(tuple, attnum,
+ 									 RelationGetDescr(catalog), &isnull);
+ 				if (!isnull)
+ 				{
+ 					Oid		namespaceId;
+ 
+ 					namespaceId = DatumGetObjectId(datum);
+ 					/* Don't report objects in temp namespaces */
+ 					if (isAnyTempNamespace(namespaceId))
+ 					{
+ 						pfree(obj);
+ 						heap_close(catalog, AccessShareLock);
+ 						MemoryContextSwitchTo(oldcxt);
+ 						return;
+ 					}
+ 
+ 					obj->schemaname = get_namespace_name(namespaceId);
+ 				}
+ 			}
+ 		}
+ 
+ 		heap_close(catalog, AccessShareLock);
+ 	}
+ 
+ 	/* object name */
+ 	obj->objidentity = getObjectIdentity(&obj->address);
+ 
+ 	/* and object type, too */
+ 	obj->objecttype = getObjectTypeDescription(&obj->address);
+ 
+ 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
+ 
+ 	MemoryContextSwitchTo(oldcxt);
+ }
+ 
+ /*
+  * pg_event_trigger_dropped_objects
+  *
+  * Make the list of dropped objects available to the user function run by the
+  * Event Trigger.
+  */
+ Datum
+ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
+ {
+ 	ReturnSetInfo	   *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ 	TupleDesc			tupdesc;
+ 	Tuplestorestate	   *tupstore;
+ 	MemoryContext		per_query_ctx;
+ 	MemoryContext		oldcontext;
+ 	slist_iter			iter;
+ 
+ 	/* XXX can this actually happen? */
+ 	if (!currentEventTriggerState)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("%s can only be called when there's a command in execution",
+ 						"pg_event_trigger_dropped_objects()")));
+ 
+ 	/* check to see if caller supports us returning a tuplestore */
+ 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("set-valued function called in context that cannot accept a set")));
+ 	if (!(rsinfo->allowedModes & SFRM_Materialize))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("materialize mode required, but it is not allowed in this context")));
+ 
+ 	/* Build a tuple descriptor for our result type */
+ 	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ 		elog(ERROR, "return type must be a row type");
+ 
+ 	/* Build tuplestore to hold the result rows */
+ 	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ 	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+ 
+ 	tupstore = tuplestore_begin_heap(true, false, work_mem);
+ 	rsinfo->returnMode = SFRM_Materialize;
+ 	rsinfo->setResult = tupstore;
+ 	rsinfo->setDesc = tupdesc;
+ 
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	slist_foreach(iter, &(currentEventTriggerState->SQLDropList))
+ 	{
+ 		SQLDropObject *obj;
+ 		int			i = 0;
+ 		Datum		values[6];
+ 		bool		nulls[6];
+ 
+ 		obj = slist_container(SQLDropObject, next, iter.cur);
+ 
+ 		MemSet(values, 0, sizeof(values));
+ 		MemSet(nulls, 0, sizeof(nulls));
+ 
+ 		/* classid */
+ 		values[i++] = ObjectIdGetDatum(obj->address.classId);
+ 
+ 		/* objid */
+ 		values[i++] = ObjectIdGetDatum(obj->address.objectId);
+ 
+ 		/* objsubid */
+ 		values[i++] = Int32GetDatum(obj->address.objectSubId);
+ 
+ 		/* object_type */
+ 		values[i++] = CStringGetTextDatum(obj->objecttype);
+ 
+ 		/* schema_name */
+ 		if (obj->schemaname)
+ 			values[i++] = CStringGetTextDatum(obj->schemaname);
+ 		else
+ 			nulls[i++] = true;
+ 
+ 		/* object_identity */
+ 		if (obj->objidentity)
+ 			values[i++] = CStringGetTextDatum(obj->objidentity);
+ 		else
+ 			nulls[i++] = true;
+ 
+ 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ 	}
+ 
+ 	/* clean up and return the tuplestore */
+ 	tuplestore_donestoring(tupstore);
+ 
+ 	return (Datum) 0;
+ }
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 5219,5230 **** opt_restart_seqs:
   *
   *	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) |
--- 5219,5231 ----
   *
   *	COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | VIEW |
   *				   COLLATION | CONVERSION | LANGUAGE | OPERATOR CLASS |
!  *				   LARGE OBJECT | 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> |
+  *				 CAST (<typename> AS <typename) |
   *				 AGGREGATE <aggname> (arg1, ...) |
   *				 FUNCTION <funcname> (arg1, arg2, ...) |
   *				 OPERATOR <op> (leftoperand_typ, rightoperand_typ) |
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 370,375 **** ProcessUtility(Node *parsetree,
--- 370,413 ----
  		} \
  	} while (0)
  
+ /*
+  * UTILITY_BEGIN_QUERY and UTILITY_END_QUERY are a pair of macros to enclose
+  * execution of a single DDL command, to ensure the event trigger environment
+  * is appropriately set up before starting, and tore down after completion or
+  * error.
+  */
+ #define UTILITY_BEGIN_QUERY(isComplete) \
+ 	do { \
+ 		bool		_isComplete = isComplete; \
+ 		EventTriggerQueryState *_prevstate = NULL; \
+ 		\
+ 		if (_isComplete) \
+ 		{ \
+ 			_prevstate = EventTriggerBeginCompleteQuery(); \
+ 		} \
+ 		\
+ 		PG_TRY(); \
+ 		{ \
+ 			/* avoid empty statement when followed by a semicolon */ \
+ 			(void) 0
+ 
+ #define UTILITY_END_QUERY() \
+ 		} \
+ 		PG_CATCH(); \
+ 		{ \
+ 			if (_isComplete) \
+ 			{ \
+ 				EventTriggerEndCompleteQuery(_prevstate, true); \
+ 			} \
+ 			PG_RE_THROW(); \
+ 		} \
+ 		PG_END_TRY(); \
+ 		if (_isComplete) \
+ 		{ \
+ 			EventTriggerEndCompleteQuery(_prevstate, false); \
+ 		} \
+ 	} while (0)
+ 
  void
  standard_ProcessUtility(Node *parsetree,
  						const char *queryString,
***************
*** 386,391 **** standard_ProcessUtility(Node *parsetree,
--- 424,431 ----
  	if (completionTag)
  		completionTag[0] = '\0';
  
+ 	UTILITY_BEGIN_QUERY(isCompleteQuery);
+ 
  	switch (nodeTag(parsetree))
  	{
  			/*
***************
*** 856,861 **** standard_ProcessUtility(Node *parsetree,
--- 896,904 ----
  					ereport(NOTICE,
  						  (errmsg("relation \"%s\" does not exist, skipping",
  								  atstmt->relation->relname)));
+ 
+ 				if (isCompleteQuery)
+ 					EventTriggerDDLCommandEnd(parsetree);
  			}
  			break;
  
***************
*** 1248,1255 **** standard_ProcessUtility(Node *parsetree,
  			break;
  
  		case T_DropOwnedStmt:
! 			/* no event triggers for global objects */
! 			DropOwnedObjects((DropOwnedStmt *) parsetree);
  			break;
  
  		case T_ReassignOwnedStmt:
--- 1291,1299 ----
  			break;
  
  		case T_DropOwnedStmt:
! 			InvokeDDLCommandEventTriggers(
! 				parsetree,
! 				DropOwnedObjects((DropOwnedStmt *) parsetree));
  			break;
  
  		case T_ReassignOwnedStmt:
***************
*** 1372,1377 **** standard_ProcessUtility(Node *parsetree,
--- 1416,1423 ----
  				 (int) nodeTag(parsetree));
  			break;
  	}
+ 
+ 	UTILITY_END_QUERY();
  }
  
  /*
*** a/src/backend/utils/adt/regproc.c
--- b/src/backend/utils/adt/regproc.c
***************
*** 304,309 **** regprocedurein(PG_FUNCTION_ARGS)
--- 304,322 ----
  char *
  format_procedure(Oid procedure_oid)
  {
+ 	return format_procedure_internal(procedure_oid, true);
+ }
+ 
+ /*
+  * Routine to produce regprocedure names; see format_procedure above.
+  *
+  * opt_qualify says whether to schema-qualify; if false, the name is never
+  * qualified, regardless of search_path visibility.  If true it is only
+  * qualified if the function is not in path.
+  */
+ char *
+ format_procedure_internal(Oid procedure_oid, bool opt_qualify)
+ {
  	char	   *result;
  	HeapTuple	proctup;
  
***************
*** 324,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);
--- 337,345 ----
  
  		/*
  		 * Would this proc be found (given the right args) by regprocedurein?
! 		 * If not, we need to qualify it -- unless caller wants it bare.
  		 */
! 		if (!opt_qualify || FunctionIsVisible(procedure_oid))
  			nspname = NULL;
  		else
  			nspname = get_namespace_name(procform->pronamespace);
*** 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
***************
*** 4690,4695 **** DATA(insert OID = 3473 (  spg_range_quad_leaf_consistent	PGNSP PGUID 12 1 0 0 0
--- 4690,4698 ----
  DESCR("SP-GiST support for quad tree over range");
  
  
+ /* event triggers */
+ DATA(insert OID = 3566 (  pg_event_trigger_dropped_objects		PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,26,25,25,25}" "{o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
+ DESCR("list objects dropped by the current command");
  /*
   * Symbolic values for provolatile column: these indicate whether the result
   * of a function is dependent *only* on the values of its explicit arguments,
*** a/src/include/commands/event_trigger.h
--- b/src/include/commands/event_trigger.h
***************
*** 13,21 ****
--- 13,29 ----
  #ifndef EVENT_TRIGGER_H
  #define EVENT_TRIGGER_H
  
+ #include "catalog/objectaddress.h"
  #include "catalog/pg_event_trigger.h"
+ #include "lib/ilist.h"
  #include "nodes/parsenodes.h"
  
+ typedef struct EventTriggerQueryState
+ {
+ 	slist_head	SQLDropList;
+ 	MemoryContext	cxt;
+ } EventTriggerQueryState;
+ 
  typedef struct EventTriggerData
  {
  	NodeTag		type;
***************
*** 43,46 **** extern bool EventTriggerSupportsObjectType(ObjectType obtype);
--- 51,59 ----
  extern void EventTriggerDDLCommandStart(Node *parsetree);
  extern void EventTriggerDDLCommandEnd(Node *parsetree);
  
+ extern EventTriggerQueryState *EventTriggerBeginCompleteQuery(void);
+ extern void EventTriggerEndCompleteQuery(EventTriggerQueryState *prevstate,
+ 							 bool abort);
+ extern void evtrig_sqldrop_add_object(ObjectAddress *object);
+ 
  #endif   /* EVENT_TRIGGER_H */
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 615,620 **** extern Datum regdictionarysend(PG_FUNCTION_ARGS);
--- 615,621 ----
  extern Datum text_regclass(PG_FUNCTION_ARGS);
  extern List *stringToQualifiedNameList(const char *string);
  extern char *format_procedure(Oid procedure_oid);
+ extern char *format_procedure_internal(Oid procedure_oid, bool opt_qualify);
  extern char *format_operator(Oid operator_oid);
  
  /* rowtypes.c */
***************
*** 1147,1152 **** extern Datum pg_describe_object(PG_FUNCTION_ARGS);
--- 1148,1156 ----
  /* commands/constraint.c */
  extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
  
+ /* commands/event_trigger.c */
+ extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
+ 
  /* commands/extension.c */
  extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
  extern Datum pg_available_extension_versions(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 */
*** a/src/test/regress/expected/event_trigger.out
--- b/src/test/regress/expected/event_trigger.out
***************
*** 93,103 **** ERROR:  event trigger "regress_event_trigger" does not exist
  drop role regression_bob;
  ERROR:  role "regression_bob" cannot be dropped because some objects depend on it
  DETAIL:  owner of event trigger regress_event_trigger3
  -- these are all OK; the second one should emit a NOTICE
  drop event trigger if exists regress_event_trigger2;
  drop event trigger if exists regress_event_trigger2;
  NOTICE:  event trigger "regress_event_trigger2" does not exist, skipping
  drop event trigger regress_event_trigger3;
  drop event trigger regress_event_trigger_end;
! drop function test_event_trigger();
  drop role regression_bob;
--- 93,224 ----
  drop role regression_bob;
  ERROR:  role "regression_bob" cannot be dropped because some objects depend on it
  DETAIL:  owner of event trigger regress_event_trigger3
+ -- cleanup before next test
  -- these are all OK; the second one should emit a NOTICE
  drop event trigger if exists regress_event_trigger2;
  drop event trigger if exists regress_event_trigger2;
  NOTICE:  event trigger "regress_event_trigger2" does not exist, skipping
  drop event trigger regress_event_trigger3;
  drop event trigger regress_event_trigger_end;
! -- test support for dropped objects
! CREATE SCHEMA schema_one authorization regression_bob;
! CREATE SCHEMA schema_two authorization regression_bob;
! CREATE SCHEMA audit_tbls authorization regression_bob;
! SET SESSION AUTHORIZATION regression_bob;
! CREATE TABLE schema_one.table_one(a int);
! CREATE TABLE schema_one.table_two(a int);
! CREATE TABLE schema_one.table_three(a int);
! CREATE TABLE audit_tbls.table_two(the_value schema_one.table_two);
! CREATE TABLE schema_two.table_two(a int);
! CREATE TABLE schema_two.table_three(a int, b text);
! CREATE TABLE audit_tbls.table_three(the_value schema_two.table_three);
! CREATE OR REPLACE FUNCTION schema_two.add(int, int) RETURNS int LANGUAGE plpgsql
!   CALLED ON NULL INPUT
!   AS $$ BEGIN RETURN coalesce($1,0) + coalesce($2,0); END; $$;
! CREATE AGGREGATE schema_two.newton
!   (BASETYPE = int, SFUNC = schema_two.add, STYPE = int);
! RESET SESSION AUTHORIZATION;
! CREATE TABLE dropped_objects (type text,
! 	schema text,
! 	object text,
! 	subobject text,
! 	curr_user text,
! 	sess_user text);
! CREATE OR REPLACE FUNCTION test_evtrig_dropped_objects() RETURNS event_trigger
! LANGUAGE plpgsql AS $$
! DECLARE
!     obj record;
! BEGIN
!     FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
!     LOOP
!         IF obj.object_type = 'table' THEN
!                 EXECUTE format('DROP TABLE IF EXISTS audit_tbls.%s',
! 			obj.object_identity);
!         END IF;
! 
! 	INSERT INTO dropped_objects
! 		(type, schema, object, curr_user, sess_user) VALUES
! 		(obj.object_type, obj.schema_name, obj.object_identity,
! 		 current_user, session_user);
!     END LOOP;
! END
! $$;
! CREATE EVENT TRIGGER regress_event_trigger_drop_objects ON ddl_command_end
! 	WHEN TAG IN ('drop table', 'drop function', 'drop view',
! 		'drop owned', 'drop schema', 'alter table')
! 	EXECUTE PROCEDURE test_evtrig_dropped_objects();
! ALTER TABLE schema_one.table_one DROP COLUMN a;
! DROP SCHEMA schema_one, schema_two CASCADE;
! NOTICE:  drop cascades to 9 other objects
! DETAIL:  drop cascades to table schema_two.table_two
! drop cascades to table schema_two.table_three
! drop cascades to table audit_tbls.table_three column the_value
! drop cascades to function schema_two.add(integer,integer)
! drop cascades to function schema_two.newton(integer)
! drop cascades to table schema_one.table_one
! drop cascades to table schema_one.table_two
! drop cascades to table audit_tbls.table_two column the_value
! drop cascades to table schema_one.table_three
! NOTICE:  table "table_two" does not exist, skipping
! CONTEXT:  SQL statement "DROP TABLE IF EXISTS audit_tbls.table_two"
! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
! SQL statement "DROP TABLE IF EXISTS audit_tbls.table_two"
! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
! NOTICE:  table "table_three" does not exist, skipping
! CONTEXT:  SQL statement "DROP TABLE IF EXISTS audit_tbls.table_three"
! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
! SQL statement "DROP TABLE IF EXISTS audit_tbls.table_three"
! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
! NOTICE:  table "table_one" does not exist, skipping
! CONTEXT:  SQL statement "DROP TABLE IF EXISTS audit_tbls.table_one"
! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
! NOTICE:  table "table_two" does not exist, skipping
! CONTEXT:  SQL statement "DROP TABLE IF EXISTS audit_tbls.table_two"
! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
! NOTICE:  table "table_three" does not exist, skipping
! CONTEXT:  SQL statement "DROP TABLE IF EXISTS audit_tbls.table_three"
! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
! SELECT * FROM dropped_objects WHERE schema IS NULL OR schema <> 'pg_toast';
!      type     |   schema   |          object          | subobject | curr_user | sess_user 
! --------------+------------+--------------------------+-----------+-----------+-----------
!  table column | schema_one | table_one.a              |           | alvherre  | alvherre
!  schema       |            | schema_two               |           | alvherre  | alvherre
!  table        | audit_tbls | table_two                |           | alvherre  | alvherre
!  type         | audit_tbls | audit_tbls.table_two     |           | alvherre  | alvherre
!  type         | audit_tbls | audit_tbls.table_two[]   |           | alvherre  | alvherre
!  table        | schema_two | table_two                |           | alvherre  | alvherre
!  type         | schema_two | schema_two.table_two     |           | alvherre  | alvherre
!  type         | schema_two | schema_two.table_two[]   |           | alvherre  | alvherre
!  table        | audit_tbls | table_three              |           | alvherre  | alvherre
!  type         | audit_tbls | audit_tbls.table_three   |           | alvherre  | alvherre
!  type         | audit_tbls | audit_tbls.table_three[] |           | alvherre  | alvherre
!  table        | schema_two | table_three              |           | alvherre  | alvherre
!  type         | schema_two | schema_two.table_three   |           | alvherre  | alvherre
!  type         | schema_two | schema_two.table_three[] |           | alvherre  | alvherre
!  table column | audit_tbls | table_three.the_value    |           | alvherre  | alvherre
!  function     | schema_two | add(integer,integer)     |           | alvherre  | alvherre
!  function     | schema_two | newton(integer)          |           | alvherre  | alvherre
!  schema       |            | schema_one               |           | alvherre  | alvherre
!  table        | schema_one | table_one                |           | alvherre  | alvherre
!  type         | schema_one | schema_one.table_one     |           | alvherre  | alvherre
!  type         | schema_one | schema_one.table_one[]   |           | alvherre  | alvherre
!  table        | schema_one | table_two                |           | alvherre  | alvherre
!  type         | schema_one | schema_one.table_two     |           | alvherre  | alvherre
!  type         | schema_one | schema_one.table_two[]   |           | alvherre  | alvherre
!  table column | audit_tbls | table_two.the_value      |           | alvherre  | alvherre
!  table        | schema_one | table_three              |           | alvherre  | alvherre
!  type         | schema_one | schema_one.table_three   |           | alvherre  | alvherre
!  type         | schema_one | schema_one.table_three[] |           | alvherre  | alvherre
! (28 rows)
! 
! drop owned by regression_bob;
! SELECT * FROM dropped_objects WHERE type = 'schema';
!   type  | schema |   object   | subobject | curr_user | sess_user 
! --------+--------+------------+-----------+-----------+-----------
!  schema |        | schema_two |           | alvherre  | alvherre
!  schema |        | schema_one |           | alvherre  | alvherre
!  schema |        | audit_tbls |           | alvherre  | alvherre
! (3 rows)
! 
  drop role regression_bob;
+ DROP EVENT TRIGGER regress_event_trigger_drop_objects;
*** a/src/test/regress/sql/event_trigger.sql
--- b/src/test/regress/sql/event_trigger.sql
***************
*** 97,106 **** drop event trigger regress_event_trigger;
  -- should fail, regression_bob owns regress_event_trigger2/3
  drop role regression_bob;
  
  -- these are all OK; the second one should emit a NOTICE
  drop event trigger if exists regress_event_trigger2;
  drop event trigger if exists regress_event_trigger2;
  drop event trigger regress_event_trigger3;
  drop event trigger regress_event_trigger_end;
! drop function test_event_trigger();
  drop role regression_bob;
--- 97,172 ----
  -- should fail, regression_bob owns regress_event_trigger2/3
  drop role regression_bob;
  
+ -- cleanup before next test
  -- these are all OK; the second one should emit a NOTICE
  drop event trigger if exists regress_event_trigger2;
  drop event trigger if exists regress_event_trigger2;
  drop event trigger regress_event_trigger3;
  drop event trigger regress_event_trigger_end;
! 
! -- test support for dropped objects
! CREATE SCHEMA schema_one authorization regression_bob;
! CREATE SCHEMA schema_two authorization regression_bob;
! CREATE SCHEMA audit_tbls authorization regression_bob;
! SET SESSION AUTHORIZATION regression_bob;
! 
! CREATE TABLE schema_one.table_one(a int);
! CREATE TABLE schema_one.table_two(a int);
! CREATE TABLE schema_one.table_three(a int);
! CREATE TABLE audit_tbls.table_two(the_value schema_one.table_two);
! 
! CREATE TABLE schema_two.table_two(a int);
! CREATE TABLE schema_two.table_three(a int, b text);
! CREATE TABLE audit_tbls.table_three(the_value schema_two.table_three);
! 
! CREATE OR REPLACE FUNCTION schema_two.add(int, int) RETURNS int LANGUAGE plpgsql
!   CALLED ON NULL INPUT
!   AS $$ BEGIN RETURN coalesce($1,0) + coalesce($2,0); END; $$;
! CREATE AGGREGATE schema_two.newton
!   (BASETYPE = int, SFUNC = schema_two.add, STYPE = int);
! 
! RESET SESSION AUTHORIZATION;
! 
! CREATE TABLE dropped_objects (type text,
! 	schema text,
! 	object text,
! 	subobject text,
! 	curr_user text,
! 	sess_user text);
! 
! CREATE OR REPLACE FUNCTION test_evtrig_dropped_objects() RETURNS event_trigger
! LANGUAGE plpgsql AS $$
! DECLARE
!     obj record;
! BEGIN
!     FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
!     LOOP
!         IF obj.object_type = 'table' THEN
!                 EXECUTE format('DROP TABLE IF EXISTS audit_tbls.%s',
! 			obj.object_identity);
!         END IF;
! 
! 	INSERT INTO dropped_objects
! 		(type, schema, object, curr_user, sess_user) VALUES
! 		(obj.object_type, obj.schema_name, obj.object_identity,
! 		 current_user, session_user);
!     END LOOP;
! END
! $$;
! 
! CREATE EVENT TRIGGER regress_event_trigger_drop_objects ON ddl_command_end
! 	WHEN TAG IN ('drop table', 'drop function', 'drop view',
! 		'drop owned', 'drop schema', 'alter table')
! 	EXECUTE PROCEDURE test_evtrig_dropped_objects();
! 
! ALTER TABLE schema_one.table_one DROP COLUMN a;
! DROP SCHEMA schema_one, schema_two CASCADE;
! 
! SELECT * FROM dropped_objects WHERE schema IS NULL OR schema <> 'pg_toast';
! 
! drop owned by regression_bob;
! SELECT * FROM dropped_objects WHERE type = 'schema';
! 
  drop role regression_bob;
+ 
+ DROP EVENT TRIGGER regress_event_trigger_drop_objects;
