diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c37e38f..3c33f9c 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -634,6 +634,10 @@ static const struct object_type_map
 	/* OCLASS_POLICY */
 	{
 		"policy", OBJECT_POLICY
+	},
+	/* OCLASS_TRANSFORM */
+	{
+		"transform", OBJECT_TRANSFORM
 	}
 };
 
@@ -1854,7 +1858,10 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 	 * object type.  Most use a simple string Values list, but there are some
 	 * exceptions.
 	 */
-	if (type == OBJECT_TYPE || type == OBJECT_DOMAIN || type == OBJECT_CAST ||
+	if (type == OBJECT_TYPE ||
+		type == OBJECT_DOMAIN ||
+		type == OBJECT_CAST ||
+		type == OBJECT_TRANSFORM ||
 		type == OBJECT_DOMCONSTRAINT)
 	{
 		Datum	   *elems;
@@ -1946,6 +1953,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 		case OBJECT_CAST:
 		case OBJECT_USER_MAPPING:
 		case OBJECT_DEFACL:
+		case OBJECT_TRANSFORM:
 			if (list_length(args) != 1)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -3599,6 +3607,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "policy");
 			break;
 
+		case OCLASS_TRANSFORM:
+			appendStringInfoString(&buffer, "transform");
+			break;
+
 		default:
 			appendStringInfo(&buffer, "unrecognized %u", object->classId);
 			break;
@@ -4521,6 +4533,40 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
+		case OCLASS_TRANSFORM:
+			{
+				Relation	transformDesc;
+				HeapTuple	tup;
+				Form_pg_transform transform;
+				char	   *transformLang;
+				char	   *transformType;
+
+				transformDesc = heap_open(TransformRelationId, AccessShareLock);
+
+				tup = get_catalog_object_by_oid(transformDesc, object->objectId);
+
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "could not find tuple for transform %u",
+						 object->objectId);
+
+				transform = (Form_pg_transform) GETSTRUCT(tup);
+
+				transformType = format_type_be_qualified(transform->trftype);
+				transformLang = get_language_name(transform->trflang, false);
+
+				appendStringInfo(&buffer, "for %s on language %s",
+								 transformType,
+								 transformLang);
+				if (objname)
+				{
+					*objname = list_make1(transformType);
+					*objargs = list_make1(pstrdup(transformLang));
+				}
+
+				heap_close(transformDesc, AccessShareLock);
+			}
+			break;
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index cc10c5e..1c4f12d 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1657,6 +1657,10 @@ EventTriggerCollectSimpleCommand(ObjectAddress address,
 	MemoryContext oldcxt;
 	CollectedCommand *command;
 
+	/* ignore if no objects to operate on */
+	if (ObjectAddressEq(address, InvalidObjectAddress))
+		return;
+
 	/* ignore if event trigger context not set, or collection disabled */
 	if (!currentEventTriggerState ||
 		currentEventTriggerState->commandCollectionInhibited)
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 4accf76..dbbb2d3 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1740,7 +1740,7 @@ check_transform_function(Form_pg_proc procstruct)
 /*
  * CREATE TRANSFORM
  */
-Oid
+ObjectAddress
 CreateTransform(CreateTransformStmt *stmt)
 {
 	Oid			typeid;
@@ -1938,7 +1938,7 @@ CreateTransform(CreateTransformStmt *stmt)
 
 	heap_close(relation, RowExclusiveLock);
 
-	return transformid;
+	return myself;
 }
 
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 7db9f96..21aa7dc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -912,8 +912,7 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
-	bool		commandCollected = false;
-	ObjectAddress address;
+	ObjectAddress address = InvalidObjectAddress;
 	ObjectAddress secondaryObject = InvalidObjectAddress;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
@@ -938,7 +937,6 @@ ProcessUtilitySlow(Node *parsetree,
 				 * EventTriggerCollectSimpleCommand called by
 				 * CreateSchemaCommand
 				 */
-				commandCollected = true;
 				break;
 
 			case T_CreateStmt:
@@ -1026,9 +1024,9 @@ ProcessUtilitySlow(Node *parsetree,
 
 					/*
 					 * The multiple commands generated here are stashed
-					 * individually, so disable collection below.
+					 * individually, so collection is disabled.
 					 */
-					commandCollected = true;
+					address = InvalidObjectAddress;
 				}
 				break;
 
@@ -1106,7 +1104,6 @@ ProcessUtilitySlow(Node *parsetree,
 				}
 
 				/* ALTER TABLE stashes commands internally */
-				commandCollected = true;
 				break;
 
 			case T_AlterDomainStmt:
@@ -1272,7 +1269,7 @@ ProcessUtilitySlow(Node *parsetree,
 					 */
 					EventTriggerCollectSimpleCommand(address, secondaryObject,
 													 parsetree);
-					commandCollected = true;
+					address = InvalidObjectAddress;
 					EventTriggerAlterTableEnd();
 				}
 				break;
@@ -1317,13 +1314,11 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_DropUserMappingStmt:
 				RemoveUserMapping((DropUserMappingStmt *) parsetree);
 				/* no commands stashed for DROP */
-				commandCollected = true;
 				break;
 
 			case T_ImportForeignSchemaStmt:
 				ImportForeignSchema((ImportForeignSchemaStmt *) parsetree);
 				/* commands are stashed inside ImportForeignSchema */
-				commandCollected = true;
 				break;
 
 			case T_CompositeTypeStmt:	/* CREATE TYPE (composite) */
@@ -1353,7 +1348,7 @@ ProcessUtilitySlow(Node *parsetree,
 				EventTriggerCollectSimpleCommand(address, secondaryObject,
 												 parsetree);
 				/* stashed internally */
-				commandCollected = true;
+				address = InvalidObjectAddress;
 				EventTriggerAlterTableEnd();
 				break;
 
@@ -1430,7 +1425,6 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_CreateOpClassStmt:
 				DefineOpClass((CreateOpClassStmt *) parsetree);
 				/* command is stashed in DefineOpClass */
-				commandCollected = true;
 				break;
 
 			case T_CreateOpFamilyStmt:
@@ -1438,13 +1432,12 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_CreateTransformStmt:
-				CreateTransform((CreateTransformStmt *) parsetree);
+				address = CreateTransform((CreateTransformStmt *) parsetree);
 				break;
 
 			case T_AlterOpFamilyStmt:
 				AlterOpFamily((AlterOpFamilyStmt *) parsetree);
 				/* commands are stashed in AlterOpFamily */
-				commandCollected = true;
 				break;
 
 			case T_AlterTSDictionaryStmt:
@@ -1458,13 +1451,11 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_AlterTableMoveAllStmt:
 				AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree);
 				/* commands are stashed in AlterTableMoveAll */
-				commandCollected = true;
 				break;
 
 			case T_DropStmt:
 				ExecDropStmt((DropStmt *) parsetree, isTopLevel);
 				/* no commands stashed for DROP */
-				commandCollected = true;
 				break;
 
 			case T_RenameStmt:
@@ -1488,19 +1479,16 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_GrantStmt:
 				ExecuteGrantStmt((GrantStmt *) parsetree);
 				/* commands are stashed in ExecGrantStmt_oids */
-				commandCollected = true;
 				break;
 
 			case T_DropOwnedStmt:
 				DropOwnedObjects((DropOwnedStmt *) parsetree);
 				/* no commands stashed for DROP */
-				commandCollected = true;
 				break;
 
 			case T_AlterDefaultPrivilegesStmt:
 				ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
 				EventTriggerCollectAlterDefPrivs((AlterDefaultPrivilegesStmt *) parsetree);
-				commandCollected = true;
 				break;
 
 			case T_CreatePolicyStmt:	/* CREATE POLICY */
@@ -1525,9 +1513,8 @@ ProcessUtilitySlow(Node *parsetree,
 		 * Remember the object so that ddl_command_end event triggers have
 		 * access to it.
 		 */
-		if (!commandCollected)
-			EventTriggerCollectSimpleCommand(address, secondaryObject,
-											 parsetree);
+		EventTriggerCollectSimpleCommand(address, secondaryObject,
+										 parsetree);
 
 		if (isCompleteQuery)
 		{
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 37808c0..de8015d 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -40,6 +40,11 @@ extern const ObjectAddress InvalidObjectAddress;
 #define ObjectAddressSet(addr, class_id, object_id) \
 	ObjectAddressSubSet(addr, class_id, object_id, 0)
 
+#define ObjectAddressEq(addr, addr2) \
+	((addr).classId == (addr2).classId && \
+	 (addr).objectId == (addr2).objectId && \
+	 (addr).objectSubId == (addr2).objectSubId)
+
 extern ObjectAddress get_object_address(ObjectType objtype, List *objname,
 				   List *objargs, Relation *relp,
 				   LOCKMODE lockmode, bool missing_ok);
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dcb6c08..9b81c16 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -51,7 +51,7 @@ extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
 extern ObjectAddress AlterFunction(AlterFunctionStmt *stmt);
 extern ObjectAddress CreateCast(CreateCastStmt *stmt);
 extern void DropCastById(Oid castOid);
-extern Oid	CreateTransform(CreateTransformStmt *stmt);
+extern ObjectAddress CreateTransform(CreateTransformStmt *stmt);
 extern void DropTransformById(Oid transformOid);
 extern void IsThereFunctionInNamespace(const char *proname, int pronargs,
 						   oidvector *proargtypes, Oid nspOid);
diff --git a/src/test/modules/test_ddl_deparse/Makefile b/src/test/modules/test_ddl_deparse/Makefile
index 13b985a..8ea6f39 100644
--- a/src/test/modules/test_ddl_deparse/Makefile
+++ b/src/test/modules/test_ddl_deparse/Makefile
@@ -15,6 +15,7 @@ REGRESS = test_ddl_deparse \
 	create_domain \
 	create_sequence_1 \
 	create_table \
+	create_transform \
 	alter_table \
 	create_view \
 	create_trigger \
diff --git a/src/test/modules/test_ddl_deparse/expected/create_transform.out b/src/test/modules/test_ddl_deparse/expected/create_transform.out
new file mode 100644
index 0000000..0d1cc36
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_transform.out
@@ -0,0 +1,13 @@
+--
+-- CREATE_TRANSFORM
+--
+-- Create a dummy transform
+-- The function FROM SQL should have internal as single argument as well
+-- as return type. The function TO SQL should have as single argument
+-- internal and as return argument the datatype of the transform done.
+-- pl/plpgsql does not authorize the use of internal as data type.
+CREATE TRANSFORM FOR int LANGUAGE SQL (
+    FROM SQL WITH FUNCTION varchar_transform(internal),
+    TO SQL WITH FUNCTION int4recv(internal));
+NOTICE:  DDL test: type simple, tag CREATE TRANSFORM
+DROP TRANSFORM FOR int LANGUAGE SQL;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_transform.sql b/src/test/modules/test_ddl_deparse/sql/create_transform.sql
new file mode 100644
index 0000000..0968702
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_transform.sql
@@ -0,0 +1,14 @@
+--
+-- CREATE_TRANSFORM
+--
+
+-- Create a dummy transform
+-- The function FROM SQL should have internal as single argument as well
+-- as return type. The function TO SQL should have as single argument
+-- internal and as return argument the datatype of the transform done.
+-- pl/plpgsql does not authorize the use of internal as data type.
+CREATE TRANSFORM FOR int LANGUAGE SQL (
+    FROM SQL WITH FUNCTION varchar_transform(internal),
+    TO SQL WITH FUNCTION int4recv(internal));
+
+DROP TRANSFORM FOR int LANGUAGE SQL;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 365dcca..034c0b1 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -32,6 +32,9 @@ CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw;
 CREATE USER MAPPING FOR regtest_addr_user SERVER "integer";
 ALTER DEFAULT PRIVILEGES FOR ROLE regtest_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regtest_addr_user;
 ALTER DEFAULT PRIVILEGES FOR ROLE regtest_addr_user REVOKE DELETE ON TABLES FROM regtest_addr_user;
+CREATE TRANSFORM FOR int LANGUAGE SQL (
+	FROM SQL WITH FUNCTION varchar_transform(internal),
+	TO SQL WITH FUNCTION int4recv(internal));
 -- test some error cases
 SELECT pg_get_object_address('stone', '{}', '{}');
 ERROR:  unrecognized object type "stone"
@@ -76,7 +79,7 @@ BEGIN
 		('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'),
 		('text search parser'), ('text search dictionary'),
 		('text search template'), ('text search configuration'),
-		('policy'), ('user mapping'), ('default acl'),
+		('policy'), ('user mapping'), ('default acl'), ('transform'),
 		('operator of access method'), ('function of access method')
 	LOOP
 		FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}')
@@ -261,6 +264,12 @@ WARNING:  error for default acl,{addr_nsp,zwei},{}: argument list length must be
 WARNING:  error for default acl,{addr_nsp,zwei},{integer}: unrecognized default ACL object type i
 WARNING:  error for default acl,{eins,zwei,drei},{}: argument list length must be exactly 1
 WARNING:  error for default acl,{eins,zwei,drei},{integer}: unrecognized default ACL object type i
+WARNING:  error for transform,{eins},{}: argument list length must be exactly 1
+WARNING:  error for transform,{eins},{integer}: type "eins" does not exist
+WARNING:  error for transform,{addr_nsp,zwei},{}: name list length must be exactly 1
+WARNING:  error for transform,{addr_nsp,zwei},{integer}: name list length must be exactly 1
+WARNING:  error for transform,{eins,zwei,drei},{}: name list length must be exactly 1
+WARNING:  error for transform,{eins,zwei,drei},{integer}: name list length must be exactly 1
 WARNING:  error for operator of access method,{eins},{}: name list length must be at least 3
 WARNING:  error for operator of access method,{eins},{integer}: name list length must be at least 3
 WARNING:  error for operator of access method,{addr_nsp,zwei},{}: name list length must be at least 3
@@ -363,7 +372,8 @@ WITH objects (type, name, args) AS (VALUES
 				('default acl', '{regtest_addr_user}', '{r}'),
 				-- extension
 				-- event trigger
-				('policy', '{addr_nsp, gentable, genpol}', '{}')
+				('policy', '{addr_nsp, gentable, genpol}', '{}'),
+				('transform', '{int}', '{sql}')
         )
 SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.subobjid)).*,
 	-- test roundtrip through pg_identify_object_as_address
@@ -411,11 +421,12 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.subobjid)).*,
  operator family           | pg_catalog | integer_ops       | pg_catalog.integer_ops USING btree                                   | t
  policy                    |            |                   | genpol on addr_nsp.gentable                                          | t
  collation                 | pg_catalog | "default"         | pg_catalog."default"                                                 | t
+ transform                 |            |                   | for integer on language sql                                          | t
  text search dictionary    | addr_nsp   | addr_ts_dict      | addr_nsp.addr_ts_dict                                                | t
  text search parser        | addr_nsp   | addr_ts_prs       | addr_nsp.addr_ts_prs                                                 | t
  text search configuration | addr_nsp   | addr_ts_conf      | addr_nsp.addr_ts_conf                                                | t
  text search template      | addr_nsp   | addr_ts_temp      | addr_nsp.addr_ts_temp                                                | t
-(40 rows)
+(41 rows)
 
 ---
 --- Cleanup resources
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 9cf8097..68e7cb0 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -36,6 +36,9 @@ CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw;
 CREATE USER MAPPING FOR regtest_addr_user SERVER "integer";
 ALTER DEFAULT PRIVILEGES FOR ROLE regtest_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regtest_addr_user;
 ALTER DEFAULT PRIVILEGES FOR ROLE regtest_addr_user REVOKE DELETE ON TABLES FROM regtest_addr_user;
+CREATE TRANSFORM FOR int LANGUAGE SQL (
+	FROM SQL WITH FUNCTION varchar_transform(internal),
+	TO SQL WITH FUNCTION int4recv(internal));
 
 -- test some error cases
 SELECT pg_get_object_address('stone', '{}', '{}');
@@ -74,7 +77,7 @@ BEGIN
 		('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'),
 		('text search parser'), ('text search dictionary'),
 		('text search template'), ('text search configuration'),
-		('policy'), ('user mapping'), ('default acl'),
+		('policy'), ('user mapping'), ('default acl'), ('transform'),
 		('operator of access method'), ('function of access method')
 	LOOP
 		FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}')
@@ -162,7 +165,8 @@ WITH objects (type, name, args) AS (VALUES
 				('default acl', '{regtest_addr_user}', '{r}'),
 				-- extension
 				-- event trigger
-				('policy', '{addr_nsp, gentable, genpol}', '{}')
+				('policy', '{addr_nsp, gentable, genpol}', '{}'),
+				('transform', '{int}', '{sql}')
         )
 SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.subobjid)).*,
 	-- test roundtrip through pg_identify_object_as_address
