From d5e784ad3cbb88aabd2e1f68dabc3ca599508945 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 25 Sep 2025 16:41:36 +0800
Subject: [PATCH v1 1/2] refactor CreateTrigger and CreateTriggerFiringOn

discussion: https://postgr.es/m/
---
 src/backend/catalog/index.c        |   3 +-
 src/backend/commands/tablecmds.c   |  14 ++-
 src/backend/commands/trigger.c     | 180 ++++++++---------------------
 src/backend/parser/gram.y          |   2 +
 src/backend/parser/parse_utilcmd.c | 144 +++++++++++++++++++++++
 src/backend/tcop/utility.c         |   2 +-
 src/include/commands/trigger.h     |   4 +-
 src/include/nodes/parsenodes.h     |   1 +
 src/include/parser/parse_utilcmd.h |   2 +
 9 files changed, 212 insertions(+), 140 deletions(-)

diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5d9db167e59..8d09d61cae2 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2039,10 +2039,11 @@ index_constraint_create(Relation heapRelation,
 		trigger->deferrable = true;
 		trigger->initdeferred = initdeferred;
 		trigger->constrrel = NULL;
+		trigger->transformed = true;
 
 		(void) CreateTrigger(trigger, NULL, RelationGetRelid(heapRelation),
 							 InvalidOid, conOid, indexRelationId, InvalidOid,
-							 InvalidOid, NULL, true, false);
+							 InvalidOid, true, false);
 	}
 
 	/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fc89352b661..bf8120c12f2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13802,10 +13802,11 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = NULL;
+	fk_trigger->transformed = true;
 
 	trigAddress = CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid,
 								constraintOid, indexOid, InvalidOid,
-								parentTrigOid, NULL, true, false);
+								parentTrigOid, true, false);
 
 	/* Make changes-so-far visible */
 	CommandCounterIncrement();
@@ -13847,6 +13848,7 @@ createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->constrrel = NULL;
+	fk_trigger->transformed = true;
 
 	switch (fkconstraint->fk_del_action)
 	{
@@ -13883,7 +13885,7 @@ createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid, Constraint *fkconstr
 
 	trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid,
 								constraintOid, indexOid, InvalidOid,
-								parentDelTrigger, NULL, true, false);
+								parentDelTrigger, true, false);
 	if (deleteTrigOid)
 		*deleteTrigOid = trigAddress.objectId;
 
@@ -13907,6 +13909,7 @@ createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->constrrel = NULL;
+	fk_trigger->transformed = true;
 
 	switch (fkconstraint->fk_upd_action)
 	{
@@ -13943,7 +13946,7 @@ createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid, Constraint *fkconstr
 
 	trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid,
 								constraintOid, indexOid, InvalidOid,
-								parentUpdTrigger, NULL, true, false);
+								parentUpdTrigger, true, false);
 	if (updateTrigOid)
 		*updateTrigOid = trigAddress.objectId;
 }
@@ -20823,15 +20826,16 @@ CloneRowTriggersToPartition(Relation parent, Relation partition)
 		trigStmt->timing = trigForm->tgtype & TRIGGER_TYPE_TIMING_MASK;
 		trigStmt->events = trigForm->tgtype & TRIGGER_TYPE_EVENT_MASK;
 		trigStmt->columns = cols;
-		trigStmt->whenClause = NULL;	/* passed separately */
+		trigStmt->whenClause = qual;
 		trigStmt->transitionRels = NIL; /* not supported at present */
 		trigStmt->deferrable = trigForm->tgdeferrable;
 		trigStmt->initdeferred = trigForm->tginitdeferred;
 		trigStmt->constrrel = NULL; /* passed separately */
+		trigStmt->transformed = true; /* whenClause alerady transformed */
 
 		CreateTriggerFiringOn(trigStmt, NULL, RelationGetRelid(partition),
 							  trigForm->tgconstrrelid, InvalidOid, InvalidOid,
-							  trigForm->tgfoid, trigForm->oid, qual,
+							  trigForm->tgfoid, trigForm->oid,
 							  false, true, trigForm->tgenabled);
 
 		MemoryContextSwitchTo(oldcxt);
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 579ac8d76ae..f1431d99a56 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -40,6 +40,7 @@
 #include "parser/parse_collate.h"
 #include "parser/parse_func.h"
 #include "parser/parse_relation.h"
+#include "parser/parse_utilcmd.h"
 #include "partitioning/partdesc.h"
 #include "pgstat.h"
 #include "rewrite/rewriteHandler.h"
@@ -139,9 +140,6 @@ static HeapTuple check_modified_virtual_generated(TupleDesc tupdesc, HeapTuple t
  * create the trigger on partitions, 2) when creating child foreign key
  * triggers; see CreateFKCheckTrigger() and createForeignKeyActionTriggers().
  *
- * If whenClause is passed, it is an already-transformed expression for
- * WHEN.  In this case, we ignore any that may come in stmt->whenClause.
- *
  * If isInternal is true then this is an internally-generated trigger.
  * This argument sets the tgisinternal field of the pg_trigger entry, and
  * if true causes us to modify the given trigger name to ensure uniqueness.
@@ -159,13 +157,13 @@ static HeapTuple check_modified_virtual_generated(TupleDesc tupdesc, HeapTuple t
 ObjectAddress
 CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 			  Oid relOid, Oid refRelOid, Oid constraintOid, Oid indexOid,
-			  Oid funcoid, Oid parentTriggerOid, Node *whenClause,
+			  Oid funcoid, Oid parentTriggerOid,
 			  bool isInternal, bool in_partition)
 {
 	return
 		CreateTriggerFiringOn(stmt, queryString, relOid, refRelOid,
 							  constraintOid, indexOid, funcoid,
-							  parentTriggerOid, whenClause, isInternal,
+							  parentTriggerOid, isInternal,
 							  in_partition, TRIGGER_FIRES_ON_ORIGIN);
 }
 
@@ -177,15 +175,15 @@ ObjectAddress
 CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 					  Oid relOid, Oid refRelOid, Oid constraintOid,
 					  Oid indexOid, Oid funcoid, Oid parentTriggerOid,
-					  Node *whenClause, bool isInternal, bool in_partition,
+					  bool isInternal, bool in_partition,
 					  char trigger_fires_when)
 {
 	int16		tgtype;
 	int			ncolumns;
 	int16	   *columns;
 	int2vector *tgattr;
-	List	   *whenRtable;
-	char	   *qual;
+	List	   *whenRtable = NIL;
+	char	   *qual = NULL;
 	Datum		values[Natts_pg_trigger];
 	bool		nulls[Natts_pg_trigger];
 	Relation	rel;
@@ -207,6 +205,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 	Oid			existing_constraint_oid = InvalidOid;
 	bool		existing_isInternal = false;
 	bool		existing_isClone = false;
+	Node 		*whenClause = NULL;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -557,133 +556,21 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 	/*
 	 * Parse the WHEN clause, if any and we weren't passed an already
 	 * transformed one.
-	 *
-	 * Note that as a side effect, we fill whenRtable when parsing.  If we got
-	 * an already parsed clause, this does not occur, which is what we want --
-	 * no point in adding redundant dependencies below.
 	 */
-	if (!whenClause && stmt->whenClause)
+	if (stmt->whenClause)
 	{
-		ParseState *pstate;
-		ParseNamespaceItem *nsitem;
-		List	   *varList;
-		ListCell   *lc;
-
-		/* Set up a pstate to parse with */
-		pstate = make_parsestate(NULL);
-		pstate->p_sourcetext = queryString;
-
-		/*
-		 * Set up nsitems for OLD and NEW references.
-		 *
-		 * 'OLD' must always have varno equal to 1 and 'NEW' equal to 2.
-		 */
-		nsitem = addRangeTableEntryForRelation(pstate, rel,
-											   AccessShareLock,
-											   makeAlias("old", NIL),
-											   false, false);
-		addNSItemToQuery(pstate, nsitem, false, true, true);
-		nsitem = addRangeTableEntryForRelation(pstate, rel,
-											   AccessShareLock,
-											   makeAlias("new", NIL),
-											   false, false);
-		addNSItemToQuery(pstate, nsitem, false, true, true);
-
-		/* Transform expression.  Copy to be sure we don't modify original */
-		whenClause = transformWhereClause(pstate,
-										  copyObject(stmt->whenClause),
-										  EXPR_KIND_TRIGGER_WHEN,
-										  "WHEN");
-		/* we have to fix its collations too */
-		assign_expr_collations(pstate, whenClause);
-
-		/*
-		 * Check for disallowed references to OLD/NEW.
-		 *
-		 * NB: pull_var_clause is okay here only because we don't allow
-		 * subselects in WHEN clauses; it would fail to examine the contents
-		 * of subselects.
-		 */
-		varList = pull_var_clause(whenClause, 0);
-		foreach(lc, varList)
+		if (!stmt->transformed)
 		{
-			Var		   *var = (Var *) lfirst(lc);
+			stmt = transformCreateTriggerStmt(RelationGetRelid(rel), stmt,
+											  queryString);
 
-			switch (var->varno)
-			{
-				case PRS2_OLD_VARNO:
-					if (!TRIGGER_FOR_ROW(tgtype))
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-								 errmsg("statement trigger's WHEN condition cannot reference column values"),
-								 parser_errposition(pstate, var->location)));
-					if (TRIGGER_FOR_INSERT(tgtype))
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-								 errmsg("INSERT trigger's WHEN condition cannot reference OLD values"),
-								 parser_errposition(pstate, var->location)));
-					/* system columns are okay here */
-					break;
-				case PRS2_NEW_VARNO:
-					if (!TRIGGER_FOR_ROW(tgtype))
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-								 errmsg("statement trigger's WHEN condition cannot reference column values"),
-								 parser_errposition(pstate, var->location)));
-					if (TRIGGER_FOR_DELETE(tgtype))
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-								 errmsg("DELETE trigger's WHEN condition cannot reference NEW values"),
-								 parser_errposition(pstate, var->location)));
-					if (var->varattno < 0 && TRIGGER_FOR_BEFORE(tgtype))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("BEFORE trigger's WHEN condition cannot reference NEW system columns"),
-								 parser_errposition(pstate, var->location)));
-					if (TRIGGER_FOR_BEFORE(tgtype) &&
-						var->varattno == 0 &&
-						RelationGetDescr(rel)->constr &&
-						(RelationGetDescr(rel)->constr->has_generated_stored ||
-						 RelationGetDescr(rel)->constr->has_generated_virtual))
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-								 errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"),
-								 errdetail("A whole-row reference is used and the table contains generated columns."),
-								 parser_errposition(pstate, var->location)));
-					if (TRIGGER_FOR_BEFORE(tgtype) &&
-						var->varattno > 0 &&
-						TupleDescAttr(RelationGetDescr(rel), var->varattno - 1)->attgenerated)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-								 errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"),
-								 errdetail("Column \"%s\" is a generated column.",
-										   NameStr(TupleDescAttr(RelationGetDescr(rel), var->varattno - 1)->attname)),
-								 parser_errposition(pstate, var->location)));
-					break;
-				default:
-					/* can't happen without add_missing_from, so just elog */
-					elog(ERROR, "trigger WHEN condition cannot contain references to other relations");
-					break;
-			}
+			whenClause = stmt->whenClause;
+			Assert(whenClause != NULL);
 		}
+		else
+			whenClause = stmt->whenClause;
 
-		/* we'll need the rtable for recordDependencyOnExpr */
-		whenRtable = pstate->p_rtable;
-
-		qual = nodeToString(whenClause);
-
-		free_parsestate(pstate);
-	}
-	else if (!whenClause)
-	{
-		whenClause = NULL;
-		whenRtable = NIL;
-		qual = NULL;
-	}
-	else
-	{
 		qual = nodeToString(whenClause);
-		whenRtable = NIL;
 	}
 
 	/*
@@ -1129,10 +1016,40 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 	 * If it has a WHEN clause, add dependencies on objects mentioned in the
 	 * expression (eg, functions, as well as any columns used).
 	 */
-	if (whenRtable != NIL)
+	if (whenClause != NULL)
+	{
+		ParseState *pstate;
+		ParseNamespaceItem *nsitem;
+
+		/* Set up a pstate to parse with */
+		pstate = make_parsestate(NULL);
+		pstate->p_sourcetext = queryString;
+
+		/*
+		 * Set up nsitems for OLD and NEW references.
+		 *
+		 * 'OLD' must always have varno equal to 1 and 'NEW' equal to 2.
+		 */
+		nsitem = addRangeTableEntryForRelation(pstate, rel,
+											   AccessShareLock,
+											   makeAlias("old", NIL),
+											   false, false);
+		addNSItemToQuery(pstate, nsitem, false, true, true);
+
+		nsitem = addRangeTableEntryForRelation(pstate, rel,
+											   AccessShareLock,
+											   makeAlias("new", NIL),
+											   false, false);
+		addNSItemToQuery(pstate, nsitem, false, true, true);
+
+		/* we'll need the rtable for recordDependencyOnExpr */
+		whenRtable = pstate->p_rtable;
 		recordDependencyOnExpr(&myself, whenClause, whenRtable,
 							   DEPENDENCY_NORMAL);
 
+		free_parsestate(pstate);
+	}
+
 	/* Post creation hook for new trigger */
 	InvokeObjectPostCreateHookArg(TriggerRelationId, trigoid, 0,
 								  isInternal);
@@ -1175,7 +1092,6 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 			 */
 			childStmt = copyObject(stmt);
 			childStmt->funcname = NIL;
-			childStmt->whenClause = NULL;
 
 			/* If there is a WHEN clause, create a modified copy of it */
 			qual = copyObject(whenClause);
@@ -1185,11 +1101,13 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 			qual = (Node *)
 				map_partition_varattnos((List *) qual, PRS2_NEW_VARNO,
 										childTbl, rel);
+			childStmt->whenClause = qual;
+			childStmt->transformed = true;
 
 			CreateTriggerFiringOn(childStmt, queryString,
 								  partdesc->oids[i], refRelOid,
 								  InvalidOid, InvalidOid,
-								  funcoid, trigoid, qual,
+								  funcoid, trigoid,
 								  isInternal, true, trigger_fires_when);
 
 			table_close(childTbl, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9fd48acb1f8..05ff0c5973f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -6070,6 +6070,7 @@ CreateTrigStmt:
 					n->deferrable = false;
 					n->initdeferred = false;
 					n->constrrel = NULL;
+					n->transformed = false;
 					$$ = (Node *) n;
 				}
 		  | CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
@@ -6120,6 +6121,7 @@ CreateTrigStmt:
 								   &n->deferrable, &n->initdeferred, &dummy,
 								   NULL, NULL, yyscanner);
 					n->constrrel = $10;
+					n->transformed = false;
 					$$ = (Node *) n;
 				}
 		;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..fd6d5c922ec 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -38,6 +38,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -3133,6 +3134,149 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
 	return stmt;
 }
 
+/*
+ * transformCreateTriggerStmt - parse analysis for CREATE TRIGGER
+ *
+ * Note: This is for parse analysis CreateTrigStmt->whenClause only, other
+ * CreateTrigStmt error checking happen in CreateTriggerFiringOn.
+ *
+ * To avoid race conditions, it's important that this function relies only on
+ * the passed-in relid (and not on stmt->relation) to determine the target
+ * relation.
+ */
+CreateTrigStmt *
+transformCreateTriggerStmt(Oid relid, CreateTrigStmt *stmt, const char *queryString)
+{
+	int16		tgtype;
+	ParseState *pstate;
+	ParseNamespaceItem *nsitem;
+	List	   *varList;
+	Relation	rel;
+
+	/* Nothing to do if statement already transformed. */
+	if (stmt->transformed)
+		return stmt;
+
+	/* Compute tgtype */
+	TRIGGER_CLEAR_TYPE(tgtype);
+	if (stmt->row)
+		TRIGGER_SETT_ROW(tgtype);
+	tgtype |= stmt->timing;
+	tgtype |= stmt->events;
+
+	/* Set up a pstate to parse with */
+	pstate = make_parsestate(NULL);
+	pstate->p_sourcetext = queryString;
+
+	/*
+	 * Put the parent table into the rtable so that the expressions can refer
+	 * to its fields without qualification.  Caller is responsible for locking
+	 * relation, but we still need to open it.
+	 */
+	rel = relation_open(relid, NoLock);
+
+	/*
+	 * Set up nsitems for OLD and NEW references.
+	 *
+	 * 'OLD' must always have varno equal to 1 and 'NEW' equal to 2.
+	*/
+	nsitem = addRangeTableEntryForRelation(pstate, rel,
+										   AccessShareLock,
+										   makeAlias("old", NIL),
+										   false, false);
+	addNSItemToQuery(pstate, nsitem, false, true, true);
+	nsitem = addRangeTableEntryForRelation(pstate, rel,
+										   AccessShareLock,
+										   makeAlias("new", NIL),
+										   false, false);
+	addNSItemToQuery(pstate, nsitem, false, true, true);
+
+	stmt->whenClause = transformWhereClause(pstate,
+											stmt->whenClause,
+											EXPR_KIND_TRIGGER_WHEN,
+											"WHEN");
+	/* we have to fix its collations too */
+	assign_expr_collations(pstate, stmt->whenClause);
+
+	/*
+	 * Check for disallowed references to OLD/NEW.
+	 *
+	 * NB: pull_var_clause is okay here only because we don't allow
+	 * subselects in WHEN clauses; it would fail to examine the contents
+	 * of subselects.
+	*/
+	varList = pull_var_clause(stmt->whenClause, 0);
+	foreach_node(Var, var, varList)
+	{
+		switch (var->varno)
+		{
+			case PRS2_OLD_VARNO:
+				if (!TRIGGER_FOR_ROW(tgtype))
+					ereport(ERROR,
+							errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							errmsg("statement trigger's WHEN condition cannot reference column values"),
+							parser_errposition(pstate, var->location));
+				if (TRIGGER_FOR_INSERT(tgtype))
+					ereport(ERROR,
+							errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							errmsg("INSERT trigger's WHEN condition cannot reference OLD values"),
+							parser_errposition(pstate, var->location));
+				/* system columns are okay here */
+				break;
+			case PRS2_NEW_VARNO:
+				if (!TRIGGER_FOR_ROW(tgtype))
+					ereport(ERROR,
+							errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							errmsg("statement trigger's WHEN condition cannot reference column values"),
+							parser_errposition(pstate, var->location));
+				if (TRIGGER_FOR_DELETE(tgtype))
+					ereport(ERROR,
+							errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							errmsg("DELETE trigger's WHEN condition cannot reference NEW values"),
+							parser_errposition(pstate, var->location));
+				if (var->varattno < 0 && TRIGGER_FOR_BEFORE(tgtype))
+					ereport(ERROR,
+							errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							errmsg("BEFORE trigger's WHEN condition cannot reference NEW system columns"),
+							parser_errposition(pstate, var->location));
+				if (TRIGGER_FOR_BEFORE(tgtype) &&
+					var->varattno == 0 &&
+					RelationGetDescr(rel)->constr &&
+					(RelationGetDescr(rel)->constr->has_generated_stored ||
+						RelationGetDescr(rel)->constr->has_generated_virtual))
+					ereport(ERROR,
+							errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"),
+							errdetail("A whole-row reference is used and the table contains generated columns."),
+							parser_errposition(pstate, var->location));
+				if (TRIGGER_FOR_BEFORE(tgtype) &&
+					var->varattno > 0 &&
+					TupleDescAttr(RelationGetDescr(rel), var->varattno - 1)->attgenerated)
+					ereport(ERROR,
+							errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"),
+							errdetail("Column \"%s\" is a generated column.",
+									  NameStr(TupleDescAttr(RelationGetDescr(rel), var->varattno - 1)->attname)),
+							parser_errposition(pstate, var->location));
+				break;
+			default:
+				/* can't happen without add_missing_from, so just elog */
+				elog(ERROR, "trigger WHEN condition cannot contain references to other relations");
+				break;
+		}
+	}
+
+	free_parsestate(pstate);
+
+	/* Close relation */
+	table_close(rel, NoLock);
+
+	/* Mark statement as successfully transformed */
+	stmt->transformed = true;
+
+	return stmt;
+}
+
 /*
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..73d47b5ebf2 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1695,7 +1695,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				address = CreateTrigger((CreateTrigStmt *) parsetree,
 										queryString, InvalidOid, InvalidOid,
 										InvalidOid, InvalidOid, InvalidOid,
-										InvalidOid, NULL, false, false);
+										InvalidOid, false, false);
 				break;
 
 			case T_CreatePLangStmt:
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index cfd7daa20ed..3f4951bca61 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -153,12 +153,12 @@ extern PGDLLIMPORT int SessionReplicationRole;
 
 extern ObjectAddress CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 								   Oid relOid, Oid refRelOid, Oid constraintOid, Oid indexOid,
-								   Oid funcoid, Oid parentTriggerOid, Node *whenClause,
+								   Oid funcoid, Oid parentTriggerOid,
 								   bool isInternal, bool in_partition);
 extern ObjectAddress CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 										   Oid relOid, Oid refRelOid, Oid constraintOid,
 										   Oid indexOid, Oid funcoid, Oid parentTriggerOid,
-										   Node *whenClause, bool isInternal, bool in_partition,
+										   bool isInternal, bool in_partition,
 										   char trigger_fires_when);
 
 extern void TriggerSetParentTrigger(Relation trigRel,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f1706df58fd..9d760e15145 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3122,6 +3122,7 @@ typedef struct CreateTrigStmt
 	bool		deferrable;		/* [NOT] DEFERRABLE */
 	bool		initdeferred;	/* INITIALLY {DEFERRED|IMMEDIATE} */
 	RangeVar   *constrrel;		/* opposite relation, if RI trigger */
+	bool		transformed;	/* true when transformCreateTriggerStmt is finished */
 } CreateTrigStmt;
 
 /* ----------------------
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..5482719d997 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -28,6 +28,8 @@ extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
 extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
 										   const char *queryString);
+extern CreateTrigStmt *transformCreateTriggerStmt(Oid relid, CreateTrigStmt *stmt,
+												  const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
 extern List *transformCreateSchemaStmtElements(List *schemaElts,
-- 
2.34.1

