/*-------------------------------------------------------------------------
 *
 * trigger.c
 *	  PostgreSQL TRIGGERs support code.
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/commands/trigger.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/relation.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "access/tableam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/partition.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/bitmapset.h"
#include "nodes/makefuncs.h"
#include "optimizer/optimizer.h"
#include "parser/parse_clause.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
#include "parser/parse_relation.h"
#include "partitioning/partdesc.h"
#include "pgstat.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc_hooks.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/plancache.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tuplestore.h"


/* GUC variables */
int			SessionReplicationRole = SESSION_REPLICATION_ROLE_ORIGIN;

/* How many levels deep into trigger execution are we? */
static int	MyTriggerDepth = 0;

/* Local function prototypes */
static void renametrig_internal(Relation tgrel, Relation targetrel,
								HeapTuple trigtup, const char *newname,
								const char *expected_name);
static void renametrig_partition(Relation tgrel, Oid partitionId,
								 Oid parentTriggerOid, const char *newname,
								 const char *expected_name);
static void SetTriggerFlags(TriggerDesc *trigdesc, Trigger *trigger);
static bool GetTupleForTrigger(EState *estate,
							   EPQState *epqstate,
							   ResultRelInfo *relinfo,
							   ItemPointer tid,
							   LockTupleMode lockmode,
							   TupleTableSlot *oldslot,
							   bool do_epq_recheck,
							   TupleTableSlot **epqslot,
							   TM_Result *tmresultp,
							   TM_FailureData *tmfdp);
static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
						   Trigger *trigger, TriggerEvent event,
						   Bitmapset *modifiedCols,
						   TupleTableSlot *oldslot, TupleTableSlot *newslot);
static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
									 int tgindx,
									 FmgrInfo *finfo,
									 Instrumentation *instr,
									 MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
								  ResultRelInfo *src_partinfo,
								  ResultRelInfo *dst_partinfo,
								  int event, bool row_trigger,
								  TupleTableSlot *oldslot, TupleTableSlot *newslot,
								  List *recheckIndexes, Bitmapset *modifiedCols,
								  TransitionCaptureState *transition_capture,
								  bool is_crosspart_update);
static void AfterTriggerEnlargeQueryState(void);
static bool before_stmt_triggers_fired(Oid relid, CmdType cmdType);
static HeapTuple check_modified_virtual_generated(TupleDesc tupdesc, HeapTuple tuple);


/*
 * Create a trigger.  Returns the address of the created trigger.
 *
 * queryString is the source text of the CREATE TRIGGER command.
 * This must be supplied if a whenClause is specified, else it can be NULL.
 *
 * relOid, if nonzero, is the relation on which the trigger should be
 * created.  If zero, the name provided in the statement will be looked up.
 *
 * refRelOid, if nonzero, is the relation to which the constraint trigger
 * refers.  If zero, the constraint relation name provided in the statement
 * will be looked up as needed.
 *
 * constraintOid, if nonzero, says that this trigger is being created
 * internally to implement that constraint.  A suitable pg_depend entry will
 * be made to link the trigger to that constraint.  constraintOid is zero when
 * executing a user-entered CREATE TRIGGER command.  (For CREATE CONSTRAINT
 * TRIGGER, we build a pg_constraint entry internally.)
 *
 * indexOid, if nonzero, is the OID of an index associated with the constraint.
 * We do nothing with this except store it into pg_trigger.tgconstrindid;
 * but when creating a trigger for a deferrable unique constraint on a
 * partitioned table, its children are looked up.  Note we don't cope with
 * invalid indexes in that case.
 *
 * funcoid, if nonzero, is the OID of the function to invoke.  When this is
 * given, stmt->funcname is ignored.
 *
 * parentTriggerOid, if nonzero, is a trigger that begets this one; so that
 * if that trigger is dropped, this one should be too.  There are two cases
 * when a nonzero value is passed for this: 1) when this function recurses to
 * 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.
 *
 * When isInternal is not true we require ACL_TRIGGER permissions on the
 * relation, as well as ACL_EXECUTE on the trigger function.  For internal
 * triggers the caller must apply any required permission checks.
 *
 * When called on partitioned tables, this function recurses to create the
 * trigger on all the partitions, except if isInternal is true, in which
 * case caller is expected to execute recursion on its own.  in_partition
 * indicates such a recursive call; outside callers should pass "false"
 * (but see CloneRowTriggersToPartition).
 */
ObjectAddress
CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
			  Oid relOid, Oid refRelOid, Oid constraintOid, Oid indexOid,
			  Oid funcoid, Oid parentTriggerOid, Node *whenClause,
			  bool isInternal, bool in_partition)
{
	return
		CreateTriggerFiringOn(stmt, queryString, relOid, refRelOid,
							  constraintOid, indexOid, funcoid,
							  parentTriggerOid, whenClause, isInternal,
							  in_partition, TRIGGER_FIRES_ON_ORIGIN);
}

/*
 * Like the above; additionally the firing condition
 * (always/origin/replica/disabled) can be specified.
 */
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,
					  char trigger_fires_when)
{
	int16		tgtype;
	int			ncolumns;
	int16	   *columns;
	int2vector *tgattr;
	List	   *whenRtable;
	char	   *qual;
	Datum		values[Natts_pg_trigger];
	bool		nulls[Natts_pg_trigger];
	Relation	rel;
	AclResult	aclresult;
	Relation	tgrel;
	Relation	pgrel;
	HeapTuple	tuple = NULL;
	Oid			funcrettype;
	Oid			trigoid = InvalidOid;
	char		internaltrigname[NAMEDATALEN];
	char	   *trigname;
	Oid			constrrelid = InvalidOid;
	ObjectAddress myself,
				referenced;
	char	   *oldtablename = NULL;
	char	   *newtablename = NULL;
	bool		partition_recurse;
	bool		trigger_exists = false;
	Oid			existing_constraint_oid = InvalidOid;
	bool		existing_isInternal = false;
	bool		existing_isClone = false;

	if (OidIsValid(relOid))
		rel = table_open(relOid, ShareRowExclusiveLock);
	else
		rel = table_openrv(stmt->relation, ShareRowExclusiveLock);

	/*
	 * Triggers must be on tables or views, and there are additional
	 * relation-type-specific restrictions.
	 */
	if (rel->rd_rel->relkind == RELKIND_RELATION)
	{
		/* Tables can't have INSTEAD OF triggers */
		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
			stmt->timing != TRIGGER_TYPE_AFTER)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a table",
							RelationGetRelationName(rel)),
					 errdetail("Tables cannot have INSTEAD OF triggers.")));
	}
	else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		/* Partitioned tables can't have INSTEAD OF triggers */
		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
			stmt->timing != TRIGGER_TYPE_AFTER)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a table",
							RelationGetRelationName(rel)),
					 errdetail("Tables cannot have INSTEAD OF triggers.")));

		/*
		 * FOR EACH ROW triggers have further restrictions
		 */
		if (stmt->row)
		{
			/*
			 * Disallow use of transition tables.
			 *
			 * Note that we have another restriction about transition tables
			 * in partitions; search for 'has_superclass' below for an
			 * explanation.  The check here is just to protect from the fact
			 * that if we allowed it here, the creation would succeed for a
			 * partitioned table with no partitions, but would be blocked by
			 * the other restriction when the first partition was created,
			 * which is very unfriendly behavior.
			 */
			if (stmt->transitionRels != NIL)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("\"%s\" is a partitioned table",
								RelationGetRelationName(rel)),
						 errdetail("ROW triggers with transition tables are not supported on partitioned tables.")));
		}
	}
	else if (rel->rd_rel->relkind == RELKIND_VIEW)
	{
		/*
		 * Views can have INSTEAD OF triggers (which we check below are
		 * row-level), or statement-level BEFORE/AFTER triggers.
		 */
		if (stmt->timing != TRIGGER_TYPE_INSTEAD && stmt->row)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a view",
							RelationGetRelationName(rel)),
					 errdetail("Views cannot have row-level BEFORE or AFTER triggers.")));
		/* Disallow TRUNCATE triggers on VIEWs */
		if (TRIGGER_FOR_TRUNCATE(stmt->events))
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a view",
							RelationGetRelationName(rel)),
					 errdetail("Views cannot have TRUNCATE triggers.")));
	}
	else if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
	{
		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
			stmt->timing != TRIGGER_TYPE_AFTER)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a foreign table",
							RelationGetRelationName(rel)),
					 errdetail("Foreign tables cannot have INSTEAD OF triggers.")));

		/*
		 * We disallow constraint triggers to protect the assumption that
		 * triggers on FKs can't be deferred.  See notes with AfterTriggers
		 * data structures, below.
		 */
		if (stmt->isconstraint)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a foreign table",
							RelationGetRelationName(rel)),
					 errdetail("Foreign tables cannot have constraint triggers.")));
	}
	else
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("relation \"%s\" cannot have triggers",
						RelationGetRelationName(rel)),
				 errdetail_relkind_not_supported(rel->rd_rel->relkind)));

	if (!allowSystemTableMods && IsSystemRelation(rel))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						RelationGetRelationName(rel))));

	if (stmt->isconstraint)
	{
		/*
		 * We must take a lock on the target relation to protect against
		 * concurrent drop.  It's not clear that AccessShareLock is strong
		 * enough, but we certainly need at least that much... otherwise, we
		 * might end up creating a pg_constraint entry referencing a
		 * nonexistent table.
		 */
		if (OidIsValid(refRelOid))
		{
			LockRelationOid(refRelOid, AccessShareLock);
			constrrelid = refRelOid;
		}
		else if (stmt->constrrel != NULL)
			constrrelid = RangeVarGetRelid(stmt->constrrel, AccessShareLock,
										   false);
	}

	/* permission checks */
	if (!isInternal)
	{
		aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
									  ACL_TRIGGER);
		if (aclresult != ACLCHECK_OK)
			aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
						   RelationGetRelationName(rel));

		if (OidIsValid(constrrelid))
		{
			aclresult = pg_class_aclcheck(constrrelid, GetUserId(),
										  ACL_TRIGGER);
			if (aclresult != ACLCHECK_OK)
				aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(constrrelid)),
							   get_rel_name(constrrelid));
		}
	}

	/*
	 * When called on a partitioned table to create a FOR EACH ROW trigger
	 * that's not internal, we create one trigger for each partition, too.
	 *
	 * For that, we'd better hold lock on all of them ahead of time.
	 */
	partition_recurse = !isInternal && stmt->row &&
		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
	if (partition_recurse)
		list_free(find_all_inheritors(RelationGetRelid(rel),
									  ShareRowExclusiveLock, NULL));

	/* Compute tgtype */
	TRIGGER_CLEAR_TYPE(tgtype);
	if (stmt->row)
		TRIGGER_SETT_ROW(tgtype);
	tgtype |= stmt->timing;
	tgtype |= stmt->events;

	/* Disallow ROW-level TRUNCATE triggers */
	if (TRIGGER_FOR_ROW(tgtype) && TRIGGER_FOR_TRUNCATE(tgtype))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("TRUNCATE FOR EACH ROW triggers are not supported")));

	/* INSTEAD triggers must be row-level, and can't have WHEN or columns */
	if (TRIGGER_FOR_INSTEAD(tgtype))
	{
		if (!TRIGGER_FOR_ROW(tgtype))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("INSTEAD OF triggers must be FOR EACH ROW")));
		if (stmt->whenClause)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("INSTEAD OF triggers cannot have WHEN conditions")));
		if (stmt->columns != NIL)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("INSTEAD OF triggers cannot have column lists")));
	}

	/*
	 * We don't yet support naming ROW transition variables, but the parser
	 * recognizes the syntax so we can give a nicer message here.
	 *
	 * Per standard, REFERENCING TABLE names are only allowed on AFTER
	 * triggers.  Per standard, REFERENCING ROW names are not allowed with FOR
	 * EACH STATEMENT.  Per standard, each OLD/NEW, ROW/TABLE permutation is
	 * only allowed once.  Per standard, OLD may not be specified when
	 * creating a trigger only for INSERT, and NEW may not be specified when
	 * creating a trigger only for DELETE.
	 *
	 * Notice that the standard allows an AFTER ... FOR EACH ROW trigger to
	 * reference both ROW and TABLE transition data.
	 */
	if (stmt->transitionRels != NIL)
	{
		List	   *varList = stmt->transitionRels;
		ListCell   *lc;

		foreach(lc, varList)
		{
			TriggerTransition *tt = lfirst_node(TriggerTransition, lc);

			if (!(tt->isTable))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("ROW variable naming in the REFERENCING clause is not supported"),
						 errhint("Use OLD TABLE or NEW TABLE for naming transition tables.")));

			/*
			 * Because of the above test, we omit further ROW-related testing
			 * below.  If we later allow naming OLD and NEW ROW variables,
			 * adjustments will be needed below.
			 */

			if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("\"%s\" is a foreign table",
								RelationGetRelationName(rel)),
						 errdetail("Triggers on foreign tables cannot have transition tables.")));

			if (rel->rd_rel->relkind == RELKIND_VIEW)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("\"%s\" is a view",
								RelationGetRelationName(rel)),
						 errdetail("Triggers on views cannot have transition tables.")));

			/*
			 * We currently don't allow row-level triggers with transition
			 * tables on partition or inheritance children.  Such triggers
			 * would somehow need to see tuples converted to the format of the
			 * table they're attached to, and it's not clear which subset of
			 * tuples each child should see.  See also the prohibitions in
			 * ATExecAttachPartition() and ATExecAddInherit().
			 */
			if (TRIGGER_FOR_ROW(tgtype) && has_superclass(rel->rd_id))
			{
				/* Use appropriate error message. */
				if (rel->rd_rel->relispartition)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("ROW triggers with transition tables are not supported on partitions")));
				else
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("ROW triggers with transition tables are not supported on inheritance children")));
			}

			if (stmt->timing != TRIGGER_TYPE_AFTER)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("transition table name can only be specified for an AFTER trigger")));

			if (TRIGGER_FOR_TRUNCATE(tgtype))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("TRUNCATE triggers with transition tables are not supported")));

			/*
			 * We currently don't allow multi-event triggers ("INSERT OR
			 * UPDATE") with transition tables, because it's not clear how to
			 * handle INSERT ... ON CONFLICT statements which can fire both
			 * INSERT and UPDATE triggers.  We show the inserted tuples to
			 * INSERT triggers and the updated tuples to UPDATE triggers, but
			 * it's not yet clear what INSERT OR UPDATE trigger should see.
			 * This restriction could be lifted if we can decide on the right
			 * semantics in a later release.
			 */
			if (((TRIGGER_FOR_INSERT(tgtype) ? 1 : 0) +
				 (TRIGGER_FOR_UPDATE(tgtype) ? 1 : 0) +
				 (TRIGGER_FOR_DELETE(tgtype) ? 1 : 0)) != 1)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("transition tables cannot be specified for triggers with more than one event")));

			/*
			 * We currently don't allow column-specific triggers with
			 * transition tables.  Per spec, that seems to require
			 * accumulating separate transition tables for each combination of
			 * columns, which is a lot of work for a rather marginal feature.
			 */
			if (stmt->columns != NIL)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("transition tables cannot be specified for triggers with column lists")));

			/*
			 * We disallow constraint triggers with transition tables, to
			 * protect the assumption that such triggers can't be deferred.
			 * See notes with AfterTriggers data structures, below.
			 *
			 * Currently this is enforced by the grammar, so just Assert here.
			 */
			Assert(!stmt->isconstraint);

			if (tt->isNew)
			{
				if (!(TRIGGER_FOR_INSERT(tgtype) ||
					  TRIGGER_FOR_UPDATE(tgtype)))
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("NEW TABLE can only be specified for an INSERT or UPDATE trigger")));

				if (newtablename != NULL)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("NEW TABLE cannot be specified multiple times")));

				newtablename = tt->name;
			}
			else
			{
				if (!(TRIGGER_FOR_DELETE(tgtype) ||
					  TRIGGER_FOR_UPDATE(tgtype)))
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("OLD TABLE can only be specified for a DELETE or UPDATE trigger")));

				if (oldtablename != NULL)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("OLD TABLE cannot be specified multiple times")));

				oldtablename = tt->name;
			}
		}

		if (newtablename != NULL && oldtablename != NULL &&
			strcmp(newtablename, oldtablename) == 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("OLD TABLE name and NEW TABLE name cannot be the same")));
	}

	/*
	 * 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)
	{
		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)
		{
			Var		   *var = (Var *) lfirst(lc);

			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;
			}
		}

		/* 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;
	}

	/*
	 * Find and validate the trigger function.
	 */
	if (!OidIsValid(funcoid))
		funcoid = LookupFuncName(stmt->funcname, 0, NULL, false);
	if (!isInternal)
	{
		aclresult = object_aclcheck(ProcedureRelationId, funcoid, GetUserId(), ACL_EXECUTE);
		if (aclresult != ACLCHECK_OK)
			aclcheck_error(aclresult, OBJECT_FUNCTION,
						   NameListToString(stmt->funcname));
	}
	funcrettype = get_func_rettype(funcoid);
	if (funcrettype != TRIGGEROID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("function %s must return type %s",
						NameListToString(stmt->funcname), "trigger")));

	/*
	 * Scan pg_trigger to see if there is already a trigger of the same name.
	 * Skip this for internally generated triggers, since we'll modify the
	 * name to be unique below.
	 *
	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
	 * the relation, so the trigger set won't be changing underneath us.
	 */
	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
	if (!isInternal)
	{
		ScanKeyData skeys[2];
		SysScanDesc tgscan;

		ScanKeyInit(&skeys[0],
					Anum_pg_trigger_tgrelid,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(RelationGetRelid(rel)));

		ScanKeyInit(&skeys[1],
					Anum_pg_trigger_tgname,
					BTEqualStrategyNumber, F_NAMEEQ,
					CStringGetDatum(stmt->trigname));

		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
									NULL, 2, skeys);

		/* There should be at most one matching tuple */
		if (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
		{
			Form_pg_trigger oldtrigger = (Form_pg_trigger) GETSTRUCT(tuple);

			trigoid = oldtrigger->oid;
			existing_constraint_oid = oldtrigger->tgconstraint;
			existing_isInternal = oldtrigger->tgisinternal;
			existing_isClone = OidIsValid(oldtrigger->tgparentid);
			trigger_exists = true;
			/* copy the tuple to use in CatalogTupleUpdate() */
			tuple = heap_copytuple(tuple);
		}
		systable_endscan(tgscan);
	}

	if (!trigger_exists)
	{
		/* Generate the OID for the new trigger. */
		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
									 Anum_pg_trigger_oid);
	}
	else
	{
		/*
		 * If OR REPLACE was specified, we'll replace the old trigger;
		 * otherwise complain about the duplicate name.
		 */
		if (!stmt->replace)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
							stmt->trigname, RelationGetRelationName(rel))));

		/*
		 * An internal trigger or a child trigger (isClone) cannot be replaced
		 * by a user-defined trigger.  However, skip this test when
		 * in_partition, because then we're recursing from a partitioned table
		 * and the check was made at the parent level.
		 */
		if ((existing_isInternal || existing_isClone) &&
			!isInternal && !in_partition)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("trigger \"%s\" for relation \"%s\" is an internal or a child trigger",
							stmt->trigname, RelationGetRelationName(rel))));

		/*
		 * It is not allowed to replace with a constraint trigger; gram.y
		 * should have enforced this already.
		 */
		Assert(!stmt->isconstraint);

		/*
		 * It is not allowed to replace an existing constraint trigger,
		 * either.  (The reason for these restrictions is partly that it seems
		 * difficult to deal with pending trigger events in such cases, and
		 * partly that the command might imply changing the constraint's
		 * properties as well, which doesn't seem nice.)
		 */
		if (OidIsValid(existing_constraint_oid))
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
							stmt->trigname, RelationGetRelationName(rel))));
	}

	/*
	 * If it's a user-entered CREATE CONSTRAINT TRIGGER command, make a
	 * corresponding pg_constraint entry.
	 */
	if (stmt->isconstraint && !OidIsValid(constraintOid))
	{
		/* Internal callers should have made their own constraints */
		Assert(!isInternal);
		constraintOid = CreateConstraintEntry(stmt->trigname,
											  RelationGetNamespace(rel),
											  CONSTRAINT_TRIGGER,
											  stmt->deferrable,
											  stmt->initdeferred,
											  true, /* Is Enforced */
											  true,
											  InvalidOid,	/* no parent */
											  RelationGetRelid(rel),
											  NULL, /* no conkey */
											  0,
											  0,
											  InvalidOid,	/* no domain */
											  InvalidOid,	/* no index */
											  InvalidOid,	/* no foreign key */
											  NULL,
											  NULL,
											  NULL,
											  NULL,
											  0,
											  ' ',
											  ' ',
											  NULL,
											  0,
											  ' ',
											  NULL, /* no exclusion */
											  NULL, /* no check constraint */
											  NULL,
											  true, /* islocal */
											  0,	/* inhcount */
											  true, /* noinherit */
											  false,	/* conperiod */
											  isInternal);	/* is_internal */
	}

	/*
	 * If trigger is internally generated, modify the provided trigger name to
	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
	 * supply a simple constant trigger name in these cases.)
	 */
	if (isInternal)
	{
		snprintf(internaltrigname, sizeof(internaltrigname),
				 "%s_%u", stmt->trigname, trigoid);
		trigname = internaltrigname;
	}
	else
	{
		/* user-defined trigger; use the specified trigger name as-is */
		trigname = stmt->trigname;
	}

	/*
	 * Build the new pg_trigger tuple.
	 */
	memset(nulls, false, sizeof(nulls));

	values[Anum_pg_trigger_oid - 1] = ObjectIdGetDatum(trigoid);
	values[Anum_pg_trigger_tgrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
	values[Anum_pg_trigger_tgparentid - 1] = ObjectIdGetDatum(parentTriggerOid);
	values[Anum_pg_trigger_tgname - 1] = DirectFunctionCall1(namein,
															 CStringGetDatum(trigname));
	values[Anum_pg_trigger_tgfoid - 1] = ObjectIdGetDatum(funcoid);
	values[Anum_pg_trigger_tgtype - 1] = Int16GetDatum(tgtype);
	values[Anum_pg_trigger_tgenabled - 1] = trigger_fires_when;
	values[Anum_pg_trigger_tgisinternal - 1] = BoolGetDatum(isInternal);
	values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(constrrelid);
	values[Anum_pg_trigger_tgconstrindid - 1] = ObjectIdGetDatum(indexOid);
	values[Anum_pg_trigger_tgconstraint - 1] = ObjectIdGetDatum(constraintOid);
	values[Anum_pg_trigger_tgdeferrable - 1] = BoolGetDatum(stmt->deferrable);
	values[Anum_pg_trigger_tginitdeferred - 1] = BoolGetDatum(stmt->initdeferred);

	if (stmt->args)
	{
		ListCell   *le;
		char	   *args;
		int16		nargs = list_length(stmt->args);
		int			len = 0;

		foreach(le, stmt->args)
		{
			char	   *ar = strVal(lfirst(le));

			len += strlen(ar) + 4;
			for (; *ar; ar++)
			{
				if (*ar == '\\')
					len++;
			}
		}
		args = (char *) palloc(len + 1);
		args[0] = '\0';
		foreach(le, stmt->args)
		{
			char	   *s = strVal(lfirst(le));
			char	   *d = args + strlen(args);

			while (*s)
			{
				if (*s == '\\')
					*d++ = '\\';
				*d++ = *s++;
			}
			strcpy(d, "\\000");
		}
		values[Anum_pg_trigger_tgnargs - 1] = Int16GetDatum(nargs);
		values[Anum_pg_trigger_tgargs - 1] = DirectFunctionCall1(byteain,
																 CStringGetDatum(args));
	}
	else
	{
		values[Anum_pg_trigger_tgnargs - 1] = Int16GetDatum(0);
		values[Anum_pg_trigger_tgargs - 1] = DirectFunctionCall1(byteain,
																 CStringGetDatum(""));
	}

	/* build column number array if it's a column-specific trigger */
	ncolumns = list_length(stmt->columns);
	if (ncolumns == 0)
		columns = NULL;
	else
	{
		ListCell   *cell;
		int			i = 0;

		columns = (int16 *) palloc(ncolumns * sizeof(int16));
		foreach(cell, stmt->columns)
		{
			char	   *name = strVal(lfirst(cell));
			int16		attnum;
			int			j;

			/* Lookup column name.  System columns are not allowed */
			attnum = attnameAttNum(rel, name, false);
			if (attnum == InvalidAttrNumber)
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_COLUMN),
						 errmsg("column \"%s\" of relation \"%s\" does not exist",
								name, RelationGetRelationName(rel))));

			/* Check for duplicates */
			for (j = i - 1; j >= 0; j--)
			{
				if (columns[j] == attnum)
					ereport(ERROR,
							(errcode(ERRCODE_DUPLICATE_COLUMN),
							 errmsg("column \"%s\" specified more than once",
									name)));
			}

			columns[i++] = attnum;
		}
	}
	tgattr = buildint2vector(columns, ncolumns);
	values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);

	/* set tgqual if trigger has WHEN clause */
	if (qual)
		values[Anum_pg_trigger_tgqual - 1] = CStringGetTextDatum(qual);
	else
		nulls[Anum_pg_trigger_tgqual - 1] = true;

	if (oldtablename)
		values[Anum_pg_trigger_tgoldtable - 1] = DirectFunctionCall1(namein,
																	 CStringGetDatum(oldtablename));
	else
		nulls[Anum_pg_trigger_tgoldtable - 1] = true;
	if (newtablename)
		values[Anum_pg_trigger_tgnewtable - 1] = DirectFunctionCall1(namein,
																	 CStringGetDatum(newtablename));
	else
		nulls[Anum_pg_trigger_tgnewtable - 1] = true;

	/*
	 * Insert or replace tuple in pg_trigger.
	 */
	if (!trigger_exists)
	{
		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
		CatalogTupleInsert(tgrel, tuple);
	}
	else
	{
		HeapTuple	newtup;

		newtup = heap_form_tuple(tgrel->rd_att, values, nulls);
		CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
		heap_freetuple(newtup);
	}

	heap_freetuple(tuple);		/* free either original or new tuple */
	table_close(tgrel, RowExclusiveLock);

	pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
	pfree(DatumGetPointer(values[Anum_pg_trigger_tgargs - 1]));
	pfree(DatumGetPointer(values[Anum_pg_trigger_tgattr - 1]));
	if (oldtablename)
		pfree(DatumGetPointer(values[Anum_pg_trigger_tgoldtable - 1]));
	if (newtablename)
		pfree(DatumGetPointer(values[Anum_pg_trigger_tgnewtable - 1]));

	/*
	 * Update relation's pg_class entry; if necessary; and if not, send an SI
	 * message to make other backends (and this one) rebuild relcache entries.
	 */
	pgrel = table_open(RelationRelationId, RowExclusiveLock);
	tuple = SearchSysCacheCopy1(RELOID,
								ObjectIdGetDatum(RelationGetRelid(rel)));
	if (!HeapTupleIsValid(tuple))
		elog(ERROR, "cache lookup failed for relation %u",
			 RelationGetRelid(rel));
	if (!((Form_pg_class) GETSTRUCT(tuple))->relhastriggers)
	{
		((Form_pg_class) GETSTRUCT(tuple))->relhastriggers = true;

		CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);

		CommandCounterIncrement();
	}
	else
		CacheInvalidateRelcacheByTuple(tuple);

	heap_freetuple(tuple);
	table_close(pgrel, RowExclusiveLock);

	/*
	 * If we're replacing a trigger, flush all the old dependencies before
	 * recording new ones.
	 */
	if (trigger_exists)
		deleteDependencyRecordsFor(TriggerRelationId, trigoid, true);

	/*
	 * Record dependencies for trigger.  Always place a normal dependency on
	 * the function.
	 */
	myself.classId = TriggerRelationId;
	myself.objectId = trigoid;
	myself.objectSubId = 0;

	referenced.classId = ProcedureRelationId;
	referenced.objectId = funcoid;
	referenced.objectSubId = 0;
	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);

	if (isInternal && OidIsValid(constraintOid))
	{
		/*
		 * Internally-generated trigger for a constraint, so make it an
		 * internal dependency of the constraint.  We can skip depending on
		 * the relation(s), as there'll be an indirect dependency via the
		 * constraint.
		 */
		referenced.classId = ConstraintRelationId;
		referenced.objectId = constraintOid;
		referenced.objectSubId = 0;
		recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
	}
	else
	{
		/*
		 * User CREATE TRIGGER, so place dependencies.  We make trigger be
		 * auto-dropped if its relation is dropped or if the FK relation is
		 * dropped.  (Auto drop is compatible with our pre-7.3 behavior.)
		 */
		referenced.classId = RelationRelationId;
		referenced.objectId = RelationGetRelid(rel);
		referenced.objectSubId = 0;
		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);

		if (OidIsValid(constrrelid))
		{
			referenced.classId = RelationRelationId;
			referenced.objectId = constrrelid;
			referenced.objectSubId = 0;
			recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
		}
		/* Not possible to have an index dependency in this case */
		Assert(!OidIsValid(indexOid));

		/*
		 * If it's a user-specified constraint trigger, make the constraint
		 * internally dependent on the trigger instead of vice versa.
		 */
		if (OidIsValid(constraintOid))
		{
			referenced.classId = ConstraintRelationId;
			referenced.objectId = constraintOid;
			referenced.objectSubId = 0;
			recordDependencyOn(&referenced, &myself, DEPENDENCY_INTERNAL);
		}

		/*
		 * If it's a partition trigger, create the partition dependencies.
		 */
		if (OidIsValid(parentTriggerOid))
		{
			ObjectAddressSet(referenced, TriggerRelationId, parentTriggerOid);
			recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_PRI);
			ObjectAddressSet(referenced, RelationRelationId, RelationGetRelid(rel));
			recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC);
		}
	}

	/* If column-specific trigger, add normal dependencies on columns */
	if (columns != NULL)
	{
		int			i;

		referenced.classId = RelationRelationId;
		referenced.objectId = RelationGetRelid(rel);
		for (i = 0; i < ncolumns; i++)
		{
			referenced.objectSubId = columns[i];
			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
		}
	}

	/*
	 * 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 (!isInternal)
			CheckUsageOnTypesInExpr(whenClause, whenRtable, GetUserId());

		recordDependencyOnExpr(&myself, whenClause, whenRtable,
							   DEPENDENCY_NORMAL);
	}

	/* Post creation hook for new trigger */
	InvokeObjectPostCreateHookArg(TriggerRelationId, trigoid, 0,
								  isInternal);

	/*
	 * Lastly, create the trigger on child relations, if needed.
	 */
	if (partition_recurse)
	{
		PartitionDesc partdesc = RelationGetPartitionDesc(rel, true);
		int			i;
		MemoryContext oldcxt,
					perChildCxt;

		perChildCxt = AllocSetContextCreate(CurrentMemoryContext,
											"part trig clone",
											ALLOCSET_SMALL_SIZES);

		/*
		 * We don't currently expect to be called with a valid indexOid.  If
		 * that ever changes then we'll need to write code here to find the
		 * corresponding child index.
		 */
		Assert(!OidIsValid(indexOid));

		oldcxt = MemoryContextSwitchTo(perChildCxt);

		/* Iterate to create the trigger on each existing partition */
		for (i = 0; i < partdesc->nparts; i++)
		{
			CreateTrigStmt *childStmt;
			Relation	childTbl;
			Node	   *qual;

			childTbl = table_open(partdesc->oids[i], ShareRowExclusiveLock);

			/*
			 * Initialize our fabricated parse node by copying the original
			 * one, then resetting fields that we pass separately.
			 */
			childStmt = copyObject(stmt);
			childStmt->funcname = NIL;
			childStmt->whenClause = NULL;

			/* If there is a WHEN clause, create a modified copy of it */
			qual = copyObject(whenClause);
			qual = (Node *)
				map_partition_varattnos((List *) qual, PRS2_OLD_VARNO,
										childTbl, rel);
			qual = (Node *)
				map_partition_varattnos((List *) qual, PRS2_NEW_VARNO,
										childTbl, rel);

			CreateTriggerFiringOn(childStmt, queryString,
								  partdesc->oids[i], refRelOid,
								  InvalidOid, InvalidOid,
								  funcoid, trigoid, qual,
								  isInternal, true, trigger_fires_when);

			table_close(childTbl, NoLock);

			MemoryContextReset(perChildCxt);
		}

		MemoryContextSwitchTo(oldcxt);
		MemoryContextDelete(perChildCxt);
	}

	/* Keep lock on target rel until end of xact */
	table_close(rel, NoLock);

	return myself;
}

/*
 * TriggerSetParentTrigger
 *		Set a partition's trigger as child of its parent trigger,
 *		or remove the linkage if parentTrigId is InvalidOid.
 *
 * This updates the constraint's pg_trigger row to show it as inherited, and
 * adds PARTITION dependencies to prevent the trigger from being deleted
 * on its own.  Alternatively, reverse that.
 */
void
TriggerSetParentTrigger(Relation trigRel,
						Oid childTrigId,
						Oid parentTrigId,
						Oid childTableId)
{
	SysScanDesc tgscan;
	ScanKeyData skey[1];
	Form_pg_trigger trigForm;
	HeapTuple	tuple,
				newtup;
	ObjectAddress depender;
	ObjectAddress referenced;

	/*
	 * Find the trigger to delete.
	 */
	ScanKeyInit(&skey[0],
				Anum_pg_trigger_oid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(childTrigId));

	tgscan = systable_beginscan(trigRel, TriggerOidIndexId, true,
								NULL, 1, skey);

	tuple = systable_getnext(tgscan);
	if (!HeapTupleIsValid(tuple))
		elog(ERROR, "could not find tuple for trigger %u", childTrigId);
	newtup = heap_copytuple(tuple);
	trigForm = (Form_pg_trigger) GETSTRUCT(newtup);
	if (OidIsValid(parentTrigId))
	{
		/* don't allow setting parent for a constraint that already has one */
		if (OidIsValid(trigForm->tgparentid))
			elog(ERROR, "trigger %u already has a parent trigger",
				 childTrigId);

		trigForm->tgparentid = parentTrigId;

		CatalogTupleUpdate(trigRel, &tuple->t_self, newtup);

		ObjectAddressSet(depender, TriggerRelationId, childTrigId);

		ObjectAddressSet(referenced, TriggerRelationId, parentTrigId);
		recordDependencyOn(&depender, &referenced, DEPENDENCY_PARTITION_PRI);

		ObjectAddressSet(referenced, RelationRelationId, childTableId);
		recordDependencyOn(&depender, &referenced, DEPENDENCY_PARTITION_SEC);
	}
	else
	{
		trigForm->tgparentid = InvalidOid;

		CatalogTupleUpdate(trigRel, &tuple->t_self, newtup);

		deleteDependencyRecordsForClass(TriggerRelationId, childTrigId,
										TriggerRelationId,
										DEPENDENCY_PARTITION_PRI);
		deleteDependencyRecordsForClass(TriggerRelationId, childTrigId,
										RelationRelationId,
										DEPENDENCY_PARTITION_SEC);
	}

	heap_freetuple(newtup);
	systable_endscan(tgscan);
}


/*
 * Guts of trigger deletion.
 */
void
RemoveTriggerById(Oid trigOid)
{
	Relation	tgrel;
	SysScanDesc tgscan;
	ScanKeyData skey[1];
	HeapTuple	tup;
	Oid			relid;
	Relation	rel;

	tgrel = table_open(TriggerRelationId, RowExclusiveLock);

	/*
	 * Find the trigger to delete.
	 */
	ScanKeyInit(&skey[0],
				Anum_pg_trigger_oid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(trigOid));

	tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
								NULL, 1, skey);

	tup = systable_getnext(tgscan);
	if (!HeapTupleIsValid(tup))
		elog(ERROR, "could not find tuple for trigger %u", trigOid);

	/*
	 * Open and exclusive-lock the relation the trigger belongs to.
	 */
	relid = ((Form_pg_trigger) GETSTRUCT(tup))->tgrelid;

	rel = table_open(relid, AccessExclusiveLock);

	if (rel->rd_rel->relkind != RELKIND_RELATION &&
		rel->rd_rel->relkind != RELKIND_VIEW &&
		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("relation \"%s\" cannot have triggers",
						RelationGetRelationName(rel)),
				 errdetail_relkind_not_supported(rel->rd_rel->relkind)));

	if (!allowSystemTableMods && IsSystemRelation(rel))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						RelationGetRelationName(rel))));

	/*
	 * Delete the pg_trigger tuple.
	 */
	CatalogTupleDelete(tgrel, &tup->t_self);

	systable_endscan(tgscan);
	table_close(tgrel, RowExclusiveLock);

	/*
	 * We do not bother to try to determine whether any other triggers remain,
	 * which would be needed in order to decide whether it's safe to clear the
	 * relation's relhastriggers.  (In any case, there might be a concurrent
	 * process adding new triggers.)  Instead, just force a relcache inval to
	 * make other backends (and this one too!) rebuild their relcache entries.
	 * There's no great harm in leaving relhastriggers true even if there are
	 * no triggers left.
	 */
	CacheInvalidateRelcache(rel);

	/* Keep lock on trigger's rel until end of xact */
	table_close(rel, NoLock);
}

/*
 * get_trigger_oid - Look up a trigger by name to find its OID.
 *
 * If missing_ok is false, throw an error if trigger not found.  If
 * true, just return InvalidOid.
 */
Oid
get_trigger_oid(Oid relid, const char *trigname, bool missing_ok)
{
	Relation	tgrel;
	ScanKeyData skey[2];
	SysScanDesc tgscan;
	HeapTuple	tup;
	Oid			oid;

	/*
	 * Find the trigger, verify permissions, set up object address
	 */
	tgrel = table_open(TriggerRelationId, AccessShareLock);

	ScanKeyInit(&skey[0],
				Anum_pg_trigger_tgrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(relid));
	ScanKeyInit(&skey[1],
				Anum_pg_trigger_tgname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(trigname));

	tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
								NULL, 2, skey);

	tup = systable_getnext(tgscan);

	if (!HeapTupleIsValid(tup))
	{
		if (!missing_ok)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("trigger \"%s\" for table \"%s\" does not exist",
							trigname, get_rel_name(relid))));
		oid = InvalidOid;
	}
	else
	{
		oid = ((Form_pg_trigger) GETSTRUCT(tup))->oid;
	}

	systable_endscan(tgscan);
	table_close(tgrel, AccessShareLock);
	return oid;
}

/*
 * Perform permissions and integrity checks before acquiring a relation lock.
 */
static void
RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
								 void *arg)
{
	HeapTuple	tuple;
	Form_pg_class form;

	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
	if (!HeapTupleIsValid(tuple))
		return;					/* concurrently dropped */
	form = (Form_pg_class) GETSTRUCT(tuple);

	/* only tables and views can have triggers */
	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
		form->relkind != RELKIND_FOREIGN_TABLE &&
		form->relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("relation \"%s\" cannot have triggers",
						rv->relname),
				 errdetail_relkind_not_supported(form->relkind)));

	/* you must own the table to rename one of its triggers */
	if (!object_ownercheck(RelationRelationId, relid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
	if (!allowSystemTableMods && IsSystemClass(relid, form))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						rv->relname)));

	ReleaseSysCache(tuple);
}

/*
 *		renametrig		- changes the name of a trigger on a relation
 *
 *		trigger name is changed in trigger catalog.
 *		No record of the previous name is kept.
 *
 *		get proper relrelation from relation catalog (if not arg)
 *		scan trigger catalog
 *				for name conflict (within rel)
 *				for original trigger (if not arg)
 *		modify tgname in trigger tuple
 *		update row in catalog
 */
ObjectAddress
renametrig(RenameStmt *stmt)
{
	Oid			tgoid;
	Relation	targetrel;
	Relation	tgrel;
	HeapTuple	tuple;
	SysScanDesc tgscan;
	ScanKeyData key[2];
	Oid			relid;
	ObjectAddress address;

	/*
	 * Look up name, check permissions, and acquire lock (which we will NOT
	 * release until end of transaction).
	 */
	relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
									 0,
									 RangeVarCallbackForRenameTrigger,
									 NULL);

	/* Have lock already, so just need to build relcache entry. */
	targetrel = relation_open(relid, NoLock);

	/*
	 * On partitioned tables, this operation recurses to partitions.  Lock all
	 * tables upfront.
	 */
	if (targetrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		(void) find_all_inheritors(relid, AccessExclusiveLock, NULL);

	tgrel = table_open(TriggerRelationId, RowExclusiveLock);

	/*
	 * Search for the trigger to modify.
	 */
	ScanKeyInit(&key[0],
				Anum_pg_trigger_tgrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(relid));
	ScanKeyInit(&key[1],
				Anum_pg_trigger_tgname,
				BTEqualStrategyNumber, F_NAMEEQ,
				PointerGetDatum(stmt->subname));
	tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
								NULL, 2, key);
	if (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
	{
		Form_pg_trigger trigform;

		trigform = (Form_pg_trigger) GETSTRUCT(tuple);
		tgoid = trigform->oid;

		/*
		 * If the trigger descends from a trigger on a parent partitioned
		 * table, reject the rename.  We don't allow a trigger in a partition
		 * to differ in name from that of its parent: that would lead to an
		 * inconsistency that pg_dump would not reproduce.
		 */
		if (OidIsValid(trigform->tgparentid))
			ereport(ERROR,
					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					errmsg("cannot rename trigger \"%s\" on table \"%s\"",
						   stmt->subname, RelationGetRelationName(targetrel)),
					errhint("Rename the trigger on the partitioned table \"%s\" instead.",
							get_rel_name(get_partition_parent(relid, false))));


		/* Rename the trigger on this relation ... */
		renametrig_internal(tgrel, targetrel, tuple, stmt->newname,
							stmt->subname);

		/* ... and if it is partitioned, recurse to its partitions */
		if (targetrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		{
			PartitionDesc partdesc = RelationGetPartitionDesc(targetrel, true);

			for (int i = 0; i < partdesc->nparts; i++)
			{
				Oid			partitionId = partdesc->oids[i];

				renametrig_partition(tgrel, partitionId, trigform->oid,
									 stmt->newname, stmt->subname);
			}
		}
	}
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("trigger \"%s\" for table \"%s\" does not exist",
						stmt->subname, RelationGetRelationName(targetrel))));
	}

	ObjectAddressSet(address, TriggerRelationId, tgoid);

	systable_endscan(tgscan);

	table_close(tgrel, RowExclusiveLock);

	/*
	 * Close rel, but keep exclusive lock!
	 */
	relation_close(targetrel, NoLock);

	return address;
}

/*
 * Subroutine for renametrig -- perform the actual work of renaming one
 * trigger on one table.
 *
 * If the trigger has a name different from the expected one, raise a
 * NOTICE about it.
 */
static void
renametrig_internal(Relation tgrel, Relation targetrel, HeapTuple trigtup,
					const char *newname, const char *expected_name)
{
	HeapTuple	tuple;
	Form_pg_trigger tgform;
	ScanKeyData key[2];
	SysScanDesc tgscan;

	/* If the trigger already has the new name, nothing to do. */
	tgform = (Form_pg_trigger) GETSTRUCT(trigtup);
	if (strcmp(NameStr(tgform->tgname), newname) == 0)
		return;

	/*
	 * Before actually trying the rename, search for triggers with the same
	 * name.  The update would fail with an ugly message in that case, and it
	 * is better to throw a nicer error.
	 */
	ScanKeyInit(&key[0],
				Anum_pg_trigger_tgrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(targetrel)));
	ScanKeyInit(&key[1],
				Anum_pg_trigger_tgname,
				BTEqualStrategyNumber, F_NAMEEQ,
				PointerGetDatum(newname));
	tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
								NULL, 2, key);
	if (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("trigger \"%s\" for relation \"%s\" already exists",
						newname, RelationGetRelationName(targetrel))));
	systable_endscan(tgscan);

	/*
	 * The target name is free; update the existing pg_trigger tuple with it.
	 */
	tuple = heap_copytuple(trigtup);	/* need a modifiable copy */
	tgform = (Form_pg_trigger) GETSTRUCT(tuple);

	/*
	 * If the trigger has a name different from what we expected, let the user
	 * know. (We can proceed anyway, since we must have reached here following
	 * a tgparentid link.)
	 */
	if (strcmp(NameStr(tgform->tgname), expected_name) != 0)
		ereport(NOTICE,
				errmsg("renamed trigger \"%s\" on relation \"%s\"",
					   NameStr(tgform->tgname),
					   RelationGetRelationName(targetrel)));

	namestrcpy(&tgform->tgname, newname);

	CatalogTupleUpdate(tgrel, &tuple->t_self, tuple);

	InvokeObjectPostAlterHook(TriggerRelationId, tgform->oid, 0);

	/*
	 * Invalidate relation's relcache entry so that other backends (and this
	 * one too!) are sent SI message to make them rebuild relcache entries.
	 * (Ideally this should happen automatically...)
	 */
	CacheInvalidateRelcache(targetrel);
}

/*
 * Subroutine for renametrig -- Helper for recursing to partitions when
 * renaming triggers on a partitioned table.
 */
static void
renametrig_partition(Relation tgrel, Oid partitionId, Oid parentTriggerOid,
					 const char *newname, const char *expected_name)
{
	SysScanDesc tgscan;
	ScanKeyData key;
	HeapTuple	tuple;

	/*
	 * Given a relation and the OID of a trigger on parent relation, find the
	 * corresponding trigger in the child and rename that trigger to the given
	 * name.
	 */
	ScanKeyInit(&key,
				Anum_pg_trigger_tgrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(partitionId));
	tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
								NULL, 1, &key);
	while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
	{
		Form_pg_trigger tgform = (Form_pg_trigger) GETSTRUCT(tuple);
		Relation	partitionRel;

		if (tgform->tgparentid != parentTriggerOid)
			continue;			/* not our trigger */

		partitionRel = table_open(partitionId, NoLock);

		/* Rename the trigger on this partition */
		renametrig_internal(tgrel, partitionRel, tuple, newname, expected_name);

		/* And if this relation is partitioned, recurse to its partitions */
		if (partitionRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		{
			PartitionDesc partdesc = RelationGetPartitionDesc(partitionRel,
															  true);

			for (int i = 0; i < partdesc->nparts; i++)
			{
				Oid			partoid = partdesc->oids[i];

				renametrig_partition(tgrel, partoid, tgform->oid, newname,
									 NameStr(tgform->tgname));
			}
		}
		table_close(partitionRel, NoLock);

		/* There should be at most one matching tuple */
		break;
	}
	systable_endscan(tgscan);
}

/*
 * EnableDisableTrigger()
 *
 *	Called by ALTER TABLE ENABLE/DISABLE [ REPLICA | ALWAYS ] TRIGGER
 *	to change 'tgenabled' field for the specified trigger(s)
 *
 * rel: relation to process (caller must hold suitable lock on it)
 * tgname: name of trigger to process, or NULL to scan all triggers
 * tgparent: if not zero, process only triggers with this tgparentid
 * fires_when: new value for tgenabled field. In addition to generic
 *			   enablement/disablement, this also defines when the trigger
 *			   should be fired in session replication roles.
 * skip_system: if true, skip "system" triggers (constraint triggers)
 * recurse: if true, recurse to partitions
 *
 * Caller should have checked permissions for the table; here we also
 * enforce that superuser privilege is required to alter the state of
 * system triggers
 */
void
EnableDisableTrigger(Relation rel, const char *tgname, Oid tgparent,
					 char fires_when, bool skip_system, bool recurse,
					 LOCKMODE lockmode)
{
	Relation	tgrel;
	int			nkeys;
	ScanKeyData keys[2];
	SysScanDesc tgscan;
	HeapTuple	tuple;
	bool		found;
	bool		changed;

	/* Scan the relevant entries in pg_triggers */
	tgrel = table_open(TriggerRelationId, RowExclusiveLock);

	ScanKeyInit(&keys[0],
				Anum_pg_trigger_tgrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(rel)));
	if (tgname)
	{
		ScanKeyInit(&keys[1],
					Anum_pg_trigger_tgname,
					BTEqualStrategyNumber, F_NAMEEQ,
					CStringGetDatum(tgname));
		nkeys = 2;
	}
	else
		nkeys = 1;

	tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
								NULL, nkeys, keys);

	found = changed = false;

	while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
	{
		Form_pg_trigger oldtrig = (Form_pg_trigger) GETSTRUCT(tuple);

		if (OidIsValid(tgparent) && tgparent != oldtrig->tgparentid)
			continue;

		if (oldtrig->tgisinternal)
		{
			/* system trigger ... ok to process? */
			if (skip_system)
				continue;
			if (!superuser())
				ereport(ERROR,
						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
						 errmsg("permission denied: \"%s\" is a system trigger",
								NameStr(oldtrig->tgname))));
		}

		found = true;

		if (oldtrig->tgenabled != fires_when)
		{
			/* need to change this one ... make a copy to scribble on */
			HeapTuple	newtup = heap_copytuple(tuple);
			Form_pg_trigger newtrig = (Form_pg_trigger) GETSTRUCT(newtup);

			newtrig->tgenabled = fires_when;

			CatalogTupleUpdate(tgrel, &newtup->t_self, newtup);

			heap_freetuple(newtup);

			changed = true;
		}

		/*
		 * When altering FOR EACH ROW triggers on a partitioned table, do the
		 * same on the partitions as well, unless ONLY is specified.
		 *
		 * Note that we recurse even if we didn't change the trigger above,
		 * because the partitions' copy of the trigger may have a different
		 * value of tgenabled than the parent's trigger and thus might need to
		 * be changed.
		 */
		if (recurse &&
			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
			(TRIGGER_FOR_ROW(oldtrig->tgtype)))
		{
			PartitionDesc partdesc = RelationGetPartitionDesc(rel, true);
			int			i;

			for (i = 0; i < partdesc->nparts; i++)
			{
				Relation	part;

				part = relation_open(partdesc->oids[i], lockmode);
				/* Match on child triggers' tgparentid, not their name */
				EnableDisableTrigger(part, NULL, oldtrig->oid,
									 fires_when, skip_system, recurse,
									 lockmode);
				table_close(part, NoLock);	/* keep lock till commit */
			}
		}

		InvokeObjectPostAlterHook(TriggerRelationId,
								  oldtrig->oid, 0);
	}

	systable_endscan(tgscan);

	table_close(tgrel, RowExclusiveLock);

	if (tgname && !found)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("trigger \"%s\" for table \"%s\" does not exist",
						tgname, RelationGetRelationName(rel))));

	/*
	 * If we changed anything, broadcast a SI inval message to force each
	 * backend (including our own!) to rebuild relation's relcache entry.
	 * Otherwise they will fail to apply the change promptly.
	 */
	if (changed)
		CacheInvalidateRelcache(rel);
}


/*
 * Build trigger data to attach to the given relcache entry.
 *
 * Note that trigger data attached to a relcache entry must be stored in
 * CacheMemoryContext to ensure it survives as long as the relcache entry.
 * But we should be running in a less long-lived working context.  To avoid
 * leaking cache memory if this routine fails partway through, we build a
 * temporary TriggerDesc in working memory and then copy the completed
 * structure into cache memory.
 */
void
RelationBuildTriggers(Relation relation)
{
	TriggerDesc *trigdesc;
	int			numtrigs;
	int			maxtrigs;
	Trigger    *triggers;
	Relation	tgrel;
	ScanKeyData skey;
	SysScanDesc tgscan;
	HeapTuple	htup;
	MemoryContext oldContext;
	int			i;

	/*
	 * Allocate a working array to hold the triggers (the array is extended if
	 * necessary)
	 */
	maxtrigs = 16;
	triggers = (Trigger *) palloc(maxtrigs * sizeof(Trigger));
	numtrigs = 0;

	/*
	 * Note: since we scan the triggers using TriggerRelidNameIndexId, we will
	 * be reading the triggers in name order, except possibly during
	 * emergency-recovery operations (ie, IgnoreSystemIndexes). This in turn
	 * ensures that triggers will be fired in name order.
	 */
	ScanKeyInit(&skey,
				Anum_pg_trigger_tgrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(relation)));

	tgrel = table_open(TriggerRelationId, AccessShareLock);
	tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
								NULL, 1, &skey);

	while (HeapTupleIsValid(htup = systable_getnext(tgscan)))
	{
		Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
		Trigger    *build;
		Datum		datum;
		bool		isnull;

		if (numtrigs >= maxtrigs)
		{
			maxtrigs *= 2;
			triggers = (Trigger *) repalloc(triggers, maxtrigs * sizeof(Trigger));
		}
		build = &(triggers[numtrigs]);

		build->tgoid = pg_trigger->oid;
		build->tgname = DatumGetCString(DirectFunctionCall1(nameout,
															NameGetDatum(&pg_trigger->tgname)));
		build->tgfoid = pg_trigger->tgfoid;
		build->tgtype = pg_trigger->tgtype;
		build->tgenabled = pg_trigger->tgenabled;
		build->tgisinternal = pg_trigger->tgisinternal;
		build->tgisclone = OidIsValid(pg_trigger->tgparentid);
		build->tgconstrrelid = pg_trigger->tgconstrrelid;
		build->tgconstrindid = pg_trigger->tgconstrindid;
		build->tgconstraint = pg_trigger->tgconstraint;
		build->tgdeferrable = pg_trigger->tgdeferrable;
		build->tginitdeferred = pg_trigger->tginitdeferred;
		build->tgnargs = pg_trigger->tgnargs;
		/* tgattr is first var-width field, so OK to access directly */
		build->tgnattr = pg_trigger->tgattr.dim1;
		if (build->tgnattr > 0)
		{
			build->tgattr = (int16 *) palloc(build->tgnattr * sizeof(int16));
			memcpy(build->tgattr, &(pg_trigger->tgattr.values),
				   build->tgnattr * sizeof(int16));
		}
		else
			build->tgattr = NULL;
		if (build->tgnargs > 0)
		{
			bytea	   *val;
			char	   *p;

			val = DatumGetByteaPP(fastgetattr(htup,
											  Anum_pg_trigger_tgargs,
											  tgrel->rd_att, &isnull));
			if (isnull)
				elog(ERROR, "tgargs is null in trigger for relation \"%s\"",
					 RelationGetRelationName(relation));
			p = (char *) VARDATA_ANY(val);
			build->tgargs = (char **) palloc(build->tgnargs * sizeof(char *));
			for (i = 0; i < build->tgnargs; i++)
			{
				build->tgargs[i] = pstrdup(p);
				p += strlen(p) + 1;
			}
		}
		else
			build->tgargs = NULL;

		datum = fastgetattr(htup, Anum_pg_trigger_tgoldtable,
							tgrel->rd_att, &isnull);
		if (!isnull)
			build->tgoldtable =
				DatumGetCString(DirectFunctionCall1(nameout, datum));
		else
			build->tgoldtable = NULL;

		datum = fastgetattr(htup, Anum_pg_trigger_tgnewtable,
							tgrel->rd_att, &isnull);
		if (!isnull)
			build->tgnewtable =
				DatumGetCString(DirectFunctionCall1(nameout, datum));
		else
			build->tgnewtable = NULL;

		datum = fastgetattr(htup, Anum_pg_trigger_tgqual,
							tgrel->rd_att, &isnull);
		if (!isnull)
			build->tgqual = TextDatumGetCString(datum);
		else
			build->tgqual = NULL;

		numtrigs++;
	}

	systable_endscan(tgscan);
	table_close(tgrel, AccessShareLock);

	/* There might not be any triggers */
	if (numtrigs == 0)
	{
		pfree(triggers);
		return;
	}

	/* Build trigdesc */
	trigdesc = (TriggerDesc *) palloc0(sizeof(TriggerDesc));
	trigdesc->triggers = triggers;
	trigdesc->numtriggers = numtrigs;
	for (i = 0; i < numtrigs; i++)
		SetTriggerFlags(trigdesc, &(triggers[i]));

	/* Copy completed trigdesc into cache storage */
	oldContext = MemoryContextSwitchTo(CacheMemoryContext);
	relation->trigdesc = CopyTriggerDesc(trigdesc);
	MemoryContextSwitchTo(oldContext);

	/* Release working memory */
	FreeTriggerDesc(trigdesc);
}

/*
 * Update the TriggerDesc's hint flags to include the specified trigger
 */
static void
SetTriggerFlags(TriggerDesc *trigdesc, Trigger *trigger)
{
	int16		tgtype = trigger->tgtype;

	trigdesc->trig_insert_before_row |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_INSERT);
	trigdesc->trig_insert_after_row |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_INSERT);
	trigdesc->trig_insert_instead_row |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_INSERT);
	trigdesc->trig_insert_before_statement |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_INSERT);
	trigdesc->trig_insert_after_statement |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_INSERT);
	trigdesc->trig_update_before_row |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_UPDATE);
	trigdesc->trig_update_after_row |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_UPDATE);
	trigdesc->trig_update_instead_row |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_UPDATE);
	trigdesc->trig_update_before_statement |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_UPDATE);
	trigdesc->trig_update_after_statement |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_UPDATE);
	trigdesc->trig_delete_before_row |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_DELETE);
	trigdesc->trig_delete_after_row |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_DELETE);
	trigdesc->trig_delete_instead_row |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_DELETE);
	trigdesc->trig_delete_before_statement |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_DELETE);
	trigdesc->trig_delete_after_statement |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_DELETE);
	/* there are no row-level truncate triggers */
	trigdesc->trig_truncate_before_statement |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_TRUNCATE);
	trigdesc->trig_truncate_after_statement |=
		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_TRUNCATE);

	trigdesc->trig_insert_new_table |=
		(TRIGGER_FOR_INSERT(tgtype) &&
		 TRIGGER_USES_TRANSITION_TABLE(trigger->tgnewtable));
	trigdesc->trig_update_old_table |=
		(TRIGGER_FOR_UPDATE(tgtype) &&
		 TRIGGER_USES_TRANSITION_TABLE(trigger->tgoldtable));
	trigdesc->trig_update_new_table |=
		(TRIGGER_FOR_UPDATE(tgtype) &&
		 TRIGGER_USES_TRANSITION_TABLE(trigger->tgnewtable));
	trigdesc->trig_delete_old_table |=
		(TRIGGER_FOR_DELETE(tgtype) &&
		 TRIGGER_USES_TRANSITION_TABLE(trigger->tgoldtable));
}

/*
 * Copy a TriggerDesc data structure.
 *
 * The copy is allocated in the current memory context.
 */
TriggerDesc *
CopyTriggerDesc(TriggerDesc *trigdesc)
{
	TriggerDesc *newdesc;
	Trigger    *trigger;
	int			i;

	if (trigdesc == NULL || trigdesc->numtriggers <= 0)
		return NULL;

	newdesc = (TriggerDesc *) palloc(sizeof(TriggerDesc));
	memcpy(newdesc, trigdesc, sizeof(TriggerDesc));

	trigger = (Trigger *) palloc(trigdesc->numtriggers * sizeof(Trigger));
	memcpy(trigger, trigdesc->triggers,
		   trigdesc->numtriggers * sizeof(Trigger));
	newdesc->triggers = trigger;

	for (i = 0; i < trigdesc->numtriggers; i++)
	{
		trigger->tgname = pstrdup(trigger->tgname);
		if (trigger->tgnattr > 0)
		{
			int16	   *newattr;

			newattr = (int16 *) palloc(trigger->tgnattr * sizeof(int16));
			memcpy(newattr, trigger->tgattr,
				   trigger->tgnattr * sizeof(int16));
			trigger->tgattr = newattr;
		}
		if (trigger->tgnargs > 0)
		{
			char	  **newargs;
			int16		j;

			newargs = (char **) palloc(trigger->tgnargs * sizeof(char *));
			for (j = 0; j < trigger->tgnargs; j++)
				newargs[j] = pstrdup(trigger->tgargs[j]);
			trigger->tgargs = newargs;
		}
		if (trigger->tgqual)
			trigger->tgqual = pstrdup(trigger->tgqual);
		if (trigger->tgoldtable)
			trigger->tgoldtable = pstrdup(trigger->tgoldtable);
		if (trigger->tgnewtable)
			trigger->tgnewtable = pstrdup(trigger->tgnewtable);
		trigger++;
	}

	return newdesc;
}

/*
 * Free a TriggerDesc data structure.
 */
void
FreeTriggerDesc(TriggerDesc *trigdesc)
{
	Trigger    *trigger;
	int			i;

	if (trigdesc == NULL)
		return;

	trigger = trigdesc->triggers;
	for (i = 0; i < trigdesc->numtriggers; i++)
	{
		pfree(trigger->tgname);
		if (trigger->tgnattr > 0)
			pfree(trigger->tgattr);
		if (trigger->tgnargs > 0)
		{
			while (--(trigger->tgnargs) >= 0)
				pfree(trigger->tgargs[trigger->tgnargs]);
			pfree(trigger->tgargs);
		}
		if (trigger->tgqual)
			pfree(trigger->tgqual);
		if (trigger->tgoldtable)
			pfree(trigger->tgoldtable);
		if (trigger->tgnewtable)
			pfree(trigger->tgnewtable);
		trigger++;
	}
	pfree(trigdesc->triggers);
	pfree(trigdesc);
}

/*
 * Compare two TriggerDesc structures for logical equality.
 */
#ifdef NOT_USED
bool
equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
{
	int			i,
				j;

	/*
	 * We need not examine the hint flags, just the trigger array itself; if
	 * we have the same triggers with the same types, the flags should match.
	 *
	 * As of 7.3 we assume trigger set ordering is significant in the
	 * comparison; so we just compare corresponding slots of the two sets.
	 *
	 * Note: comparing the stringToNode forms of the WHEN clauses means that
	 * parse column locations will affect the result.  This is okay as long as
	 * this function is only used for detecting exact equality, as for example
	 * in checking for staleness of a cache entry.
	 */
	if (trigdesc1 != NULL)
	{
		if (trigdesc2 == NULL)
			return false;
		if (trigdesc1->numtriggers != trigdesc2->numtriggers)
			return false;
		for (i = 0; i < trigdesc1->numtriggers; i++)
		{
			Trigger    *trig1 = trigdesc1->triggers + i;
			Trigger    *trig2 = trigdesc2->triggers + i;

			if (trig1->tgoid != trig2->tgoid)
				return false;
			if (strcmp(trig1->tgname, trig2->tgname) != 0)
				return false;
			if (trig1->tgfoid != trig2->tgfoid)
				return false;
			if (trig1->tgtype != trig2->tgtype)
				return false;
			if (trig1->tgenabled != trig2->tgenabled)
				return false;
			if (trig1->tgisinternal != trig2->tgisinternal)
				return false;
			if (trig1->tgisclone != trig2->tgisclone)
				return false;
			if (trig1->tgconstrrelid != trig2->tgconstrrelid)
				return false;
			if (trig1->tgconstrindid != trig2->tgconstrindid)
				return false;
			if (trig1->tgconstraint != trig2->tgconstraint)
				return false;
			if (trig1->tgdeferrable != trig2->tgdeferrable)
				return false;
			if (trig1->tginitdeferred != trig2->tginitdeferred)
				return false;
			if (trig1->tgnargs != trig2->tgnargs)
				return false;
			if (trig1->tgnattr != trig2->tgnattr)
				return false;
			if (trig1->tgnattr > 0 &&
				memcmp(trig1->tgattr, trig2->tgattr,
					   trig1->tgnattr * sizeof(int16)) != 0)
				return false;
			for (j = 0; j < trig1->tgnargs; j++)
				if (strcmp(trig1->tgargs[j], trig2->tgargs[j]) != 0)
					return false;
			if (trig1->tgqual == NULL && trig2->tgqual == NULL)
				 /* ok */ ;
			else if (trig1->tgqual == NULL || trig2->tgqual == NULL)
				return false;
			else if (strcmp(trig1->tgqual, trig2->tgqual) != 0)
				return false;
			if (trig1->tgoldtable == NULL && trig2->tgoldtable == NULL)
				 /* ok */ ;
			else if (trig1->tgoldtable == NULL || trig2->tgoldtable == NULL)
				return false;
			else if (strcmp(trig1->tgoldtable, trig2->tgoldtable) != 0)
				return false;
			if (trig1->tgnewtable == NULL && trig2->tgnewtable == NULL)
				 /* ok */ ;
			else if (trig1->tgnewtable == NULL || trig2->tgnewtable == NULL)
				return false;
			else if (strcmp(trig1->tgnewtable, trig2->tgnewtable) != 0)
				return false;
		}
	}
	else if (trigdesc2 != NULL)
		return false;
	return true;
}
#endif							/* NOT_USED */

/*
 * Check if there is a row-level trigger with transition tables that prevents
 * a table from becoming an inheritance child or partition.  Return the name
 * of the first such incompatible trigger, or NULL if there is none.
 */
const char *
FindTriggerIncompatibleWithInheritance(TriggerDesc *trigdesc)
{
	if (trigdesc != NULL)
	{
		int			i;

		for (i = 0; i < trigdesc->numtriggers; ++i)
		{
			Trigger    *trigger = &trigdesc->triggers[i];

			if (!TRIGGER_FOR_ROW(trigger->tgtype))
				continue;
			if (trigger->tgoldtable != NULL || trigger->tgnewtable != NULL)
				return trigger->tgname;
		}
	}

	return NULL;
}

/*
 * Call a trigger function.
 *
 *		trigdata: trigger descriptor.
 *		tgindx: trigger's index in finfo and instr arrays.
 *		finfo: array of cached trigger function call information.
 *		instr: optional array of EXPLAIN ANALYZE instrumentation state.
 *		per_tuple_context: memory context to execute the function in.
 *
 * Returns the tuple (or NULL) as returned by the function.
 */
static HeapTuple
ExecCallTriggerFunc(TriggerData *trigdata,
					int tgindx,
					FmgrInfo *finfo,
					Instrumentation *instr,
					MemoryContext per_tuple_context)
{
	LOCAL_FCINFO(fcinfo, 0);
	PgStat_FunctionCallUsage fcusage;
	Datum		result;
	MemoryContext oldContext;

	/*
	 * Protect against code paths that may fail to initialize transition table
	 * info.
	 */
	Assert(((TRIGGER_FIRED_BY_INSERT(trigdata->tg_event) ||
			 TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event) ||
			 TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) &&
			TRIGGER_FIRED_AFTER(trigdata->tg_event) &&
			!(trigdata->tg_event & AFTER_TRIGGER_DEFERRABLE) &&
			!(trigdata->tg_event & AFTER_TRIGGER_INITDEFERRED)) ||
		   (trigdata->tg_oldtable == NULL && trigdata->tg_newtable == NULL));

	finfo += tgindx;

	/*
	 * We cache fmgr lookup info, to avoid making the lookup again on each
	 * call.
	 */
	if (finfo->fn_oid == InvalidOid)
		fmgr_info(trigdata->tg_trigger->tgfoid, finfo);

	Assert(finfo->fn_oid == trigdata->tg_trigger->tgfoid);

	/*
	 * If doing EXPLAIN ANALYZE, start charging time to this trigger.
	 */
	if (instr)
		InstrStartNode(instr + tgindx);

	/*
	 * Do the function evaluation in the per-tuple memory context, so that
	 * leaked memory will be reclaimed once per tuple. Note in particular that
	 * any new tuple created by the trigger function will live till the end of
	 * the tuple cycle.
	 */
	oldContext = MemoryContextSwitchTo(per_tuple_context);

	/*
	 * Call the function, passing no arguments but setting a context.
	 */
	InitFunctionCallInfoData(*fcinfo, finfo, 0,
							 InvalidOid, (Node *) trigdata, NULL);

	pgstat_init_function_usage(fcinfo, &fcusage);

	MyTriggerDepth++;
	PG_TRY();
	{
		result = FunctionCallInvoke(fcinfo);
	}
	PG_FINALLY();
	{
		MyTriggerDepth--;
	}
	PG_END_TRY();

	pgstat_end_function_usage(&fcusage, true);

	MemoryContextSwitchTo(oldContext);

	/*
	 * Trigger protocol allows function to return a null pointer, but NOT to
	 * set the isnull result flag.
	 */
	if (fcinfo->isnull)
		ereport(ERROR,
				(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
				 errmsg("trigger function %u returned null value",
						fcinfo->flinfo->fn_oid)));

	/*
	 * If doing EXPLAIN ANALYZE, stop charging time to this trigger, and count
	 * one "tuple returned" (really the number of firings).
	 */
	if (instr)
		InstrStopNode(instr + tgindx, 1);

	return (HeapTuple) DatumGetPointer(result);
}

void
ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
{
	TriggerDesc *trigdesc;
	int			i;
	TriggerData LocTriggerData = {0};

	trigdesc = relinfo->ri_TrigDesc;

	if (trigdesc == NULL)
		return;
	if (!trigdesc->trig_insert_before_statement)
		return;

	/* no-op if we already fired BS triggers in this context */
	if (before_stmt_triggers_fired(RelationGetRelid(relinfo->ri_RelationDesc),
								   CMD_INSERT))
		return;

	LocTriggerData.type = T_TriggerData;
	LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
		TRIGGER_EVENT_BEFORE;
	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
	for (i = 0; i < trigdesc->numtriggers; i++)
	{
		Trigger    *trigger = &trigdesc->triggers[i];
		HeapTuple	newtuple;

		if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
								  TRIGGER_TYPE_STATEMENT,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_INSERT))
			continue;
		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
							NULL, NULL, NULL))
			continue;

		LocTriggerData.tg_trigger = trigger;
		newtuple = ExecCallTriggerFunc(&LocTriggerData,
									   i,
									   relinfo->ri_TrigFunctions,
									   relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(estate));

		if (newtuple)
			ereport(ERROR,
					(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
					 errmsg("BEFORE STATEMENT trigger cannot return a value")));
	}
}

void
ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo,
					 TransitionCaptureState *transition_capture)
{
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;

	if (trigdesc && trigdesc->trig_insert_after_statement)
		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
							  TRIGGER_EVENT_INSERT,
							  false, NULL, NULL, NIL, NULL, transition_capture,
							  false);
}

bool
ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
					 TupleTableSlot *slot)
{
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
	HeapTuple	newtuple = NULL;
	bool		should_free;
	TriggerData LocTriggerData = {0};
	int			i;

	LocTriggerData.type = T_TriggerData;
	LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
		TRIGGER_EVENT_ROW |
		TRIGGER_EVENT_BEFORE;
	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
	for (i = 0; i < trigdesc->numtriggers; i++)
	{
		Trigger    *trigger = &trigdesc->triggers[i];
		HeapTuple	oldtuple;

		if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
								  TRIGGER_TYPE_ROW,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_INSERT))
			continue;
		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
							NULL, NULL, slot))
			continue;

		if (!newtuple)
			newtuple = ExecFetchSlotHeapTuple(slot, true, &should_free);

		LocTriggerData.tg_trigslot = slot;
		LocTriggerData.tg_trigtuple = oldtuple = newtuple;
		LocTriggerData.tg_trigger = trigger;
		newtuple = ExecCallTriggerFunc(&LocTriggerData,
									   i,
									   relinfo->ri_TrigFunctions,
									   relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(estate));
		if (newtuple == NULL)
		{
			if (should_free)
				heap_freetuple(oldtuple);
			return false;		/* "do nothing" */
		}
		else if (newtuple != oldtuple)
		{
			newtuple = check_modified_virtual_generated(RelationGetDescr(relinfo->ri_RelationDesc), newtuple);

			ExecForceStoreHeapTuple(newtuple, slot, false);

			/*
			 * After a tuple in a partition goes through a trigger, the user
			 * could have changed the partition key enough that the tuple no
			 * longer fits the partition.  Verify that.
			 */
			if (trigger->tgisclone &&
				!ExecPartitionCheck(relinfo, slot, estate, false))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("moving row to another partition during a BEFORE FOR EACH ROW trigger is not supported"),
						 errdetail("Before executing trigger \"%s\", the row was to be in partition \"%s.%s\".",
								   trigger->tgname,
								   get_namespace_name(RelationGetNamespace(relinfo->ri_RelationDesc)),
								   RelationGetRelationName(relinfo->ri_RelationDesc))));

			if (should_free)
				heap_freetuple(oldtuple);

			/* signal tuple should be re-fetched if used */
			newtuple = NULL;
		}
	}

	return true;
}

void
ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
					 TupleTableSlot *slot, List *recheckIndexes,
					 TransitionCaptureState *transition_capture)
{
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;

	if (relinfo->ri_FdwRoutine && transition_capture &&
		transition_capture->tcs_insert_new_table)
	{
		Assert(relinfo->ri_RootResultRelInfo);
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot collect transition tuples from child foreign tables")));
	}

	if ((trigdesc && trigdesc->trig_insert_after_row) ||
		(transition_capture && transition_capture->tcs_insert_new_table))
		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
							  TRIGGER_EVENT_INSERT,
							  true, NULL, slot,
							  recheckIndexes, NULL,
							  transition_capture,
							  false);
}

bool
ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
					 TupleTableSlot *slot)
{
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
	HeapTuple	newtuple = NULL;
	bool		should_free;
	TriggerData LocTriggerData = {0};
	int			i;

	LocTriggerData.type = T_TriggerData;
	LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
		TRIGGER_EVENT_ROW |
		TRIGGER_EVENT_INSTEAD;
	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
	for (i = 0; i < trigdesc->numtriggers; i++)
	{
		Trigger    *trigger = &trigdesc->triggers[i];
		HeapTuple	oldtuple;

		if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
								  TRIGGER_TYPE_ROW,
								  TRIGGER_TYPE_INSTEAD,
								  TRIGGER_TYPE_INSERT))
			continue;
		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
							NULL, NULL, slot))
			continue;

		if (!newtuple)
			newtuple = ExecFetchSlotHeapTuple(slot, true, &should_free);

		LocTriggerData.tg_trigslot = slot;
		LocTriggerData.tg_trigtuple = oldtuple = newtuple;
		LocTriggerData.tg_trigger = trigger;
		newtuple = ExecCallTriggerFunc(&LocTriggerData,
									   i,
									   relinfo->ri_TrigFunctions,
									   relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(estate));
		if (newtuple == NULL)
		{
			if (should_free)
				heap_freetuple(oldtuple);
			return false;		/* "do nothing" */
		}
		else if (newtuple != oldtuple)
		{
			ExecForceStoreHeapTuple(newtuple, slot, false);

			if (should_free)
				heap_freetuple(oldtuple);

			/* signal tuple should be re-fetched if used */
			newtuple = NULL;
		}
	}

	return true;
}

void
ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
{
	TriggerDesc *trigdesc;
	int			i;
	TriggerData LocTriggerData = {0};

	trigdesc = relinfo->ri_TrigDesc;

	if (trigdesc == NULL)
		return;
	if (!trigdesc->trig_delete_before_statement)
		return;

	/* no-op if we already fired BS triggers in this context */
	if (before_stmt_triggers_fired(RelationGetRelid(relinfo->ri_RelationDesc),
								   CMD_DELETE))
		return;

	LocTriggerData.type = T_TriggerData;
	LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
		TRIGGER_EVENT_BEFORE;
	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
	for (i = 0; i < trigdesc->numtriggers; i++)
	{
		Trigger    *trigger = &trigdesc->triggers[i];
		HeapTuple	newtuple;

		if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
								  TRIGGER_TYPE_STATEMENT,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_DELETE))
			continue;
		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
							NULL, NULL, NULL))
			continue;

		LocTriggerData.tg_trigger = trigger;
		newtuple = ExecCallTriggerFunc(&LocTriggerData,
									   i,
									   relinfo->ri_TrigFunctions,
									   relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(estate));

		if (newtuple)
			ereport(ERROR,
					(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
					 errmsg("BEFORE STATEMENT trigger cannot return a value")));
	}
}

void
ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
					 TransitionCaptureState *transition_capture)
{
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;

	if (trigdesc && trigdesc->trig_delete_after_statement)
		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
							  TRIGGER_EVENT_DELETE,
							  false, NULL, NULL, NIL, NULL, transition_capture,
							  false);
}

/*
 * Execute BEFORE ROW DELETE triggers.
 *
 * True indicates caller can proceed with the delete.  False indicates caller
 * need to suppress the delete and additionally if requested, we need to pass
 * back the concurrently updated tuple if any.
 */
bool
ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
					 ResultRelInfo *relinfo,
					 ItemPointer tupleid,
					 HeapTuple fdw_trigtuple,
					 TupleTableSlot **epqslot,
					 TM_Result *tmresult,
					 TM_FailureData *tmfd,
					 bool is_merge_delete)
{
	TupleTableSlot *slot = ExecGetTriggerOldSlot(estate, relinfo);
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
	bool		result = true;
	TriggerData LocTriggerData = {0};
	HeapTuple	trigtuple;
	bool		should_free = false;
	int			i;

	Assert(HeapTupleIsValid(fdw_trigtuple) ^ ItemPointerIsValid(tupleid));
	if (fdw_trigtuple == NULL)
	{
		TupleTableSlot *epqslot_candidate = NULL;

		/*
		 * Get a copy of the on-disk tuple we are planning to delete.  In
		 * general, if the tuple has been concurrently updated, we should
		 * recheck it using EPQ.  However, if this is a MERGE DELETE action,
		 * we skip this EPQ recheck and leave it to the caller (it must do
		 * additional rechecking, and might end up executing a different
		 * action entirely).
		 */
		if (!GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
								LockTupleExclusive, slot, !is_merge_delete,
								&epqslot_candidate, tmresult, tmfd))
			return false;

		/*
		 * If the tuple was concurrently updated and the caller of this
		 * function requested for the updated tuple, skip the trigger
		 * execution.
		 */
		if (epqslot_candidate != NULL && epqslot != NULL)
		{
			*epqslot = epqslot_candidate;
			return false;
		}

		trigtuple = ExecFetchSlotHeapTuple(slot, true, &should_free);
	}
	else
	{
		trigtuple = fdw_trigtuple;
		ExecForceStoreHeapTuple(trigtuple, slot, false);
	}

	LocTriggerData.type = T_TriggerData;
	LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
		TRIGGER_EVENT_ROW |
		TRIGGER_EVENT_BEFORE;
	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
	for (i = 0; i < trigdesc->numtriggers; i++)
	{
		HeapTuple	newtuple;
		Trigger    *trigger = &trigdesc->triggers[i];

		if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
								  TRIGGER_TYPE_ROW,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_DELETE))
			continue;
		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
							NULL, slot, NULL))
			continue;

		LocTriggerData.tg_trigslot = slot;
		LocTriggerData.tg_trigtuple = trigtuple;
		LocTriggerData.tg_trigger = trigger;
		newtuple = ExecCallTriggerFunc(&LocTriggerData,
									   i,
									   relinfo->ri_TrigFunctions,
									   relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(estate));
		if (newtuple == NULL)
		{
			result = false;		/* tell caller to suppress delete */
			break;
		}
		if (newtuple != trigtuple)
			heap_freetuple(newtuple);
	}
	if (should_free)
		heap_freetuple(trigtuple);

	return result;
}

/*
 * Note: is_crosspart_update must be true if the DELETE is being performed
 * as part of a cross-partition update.
 */
void
ExecARDeleteTriggers(EState *estate,
					 ResultRelInfo *relinfo,
					 ItemPointer tupleid,
					 HeapTuple fdw_trigtuple,
					 TransitionCaptureState *transition_capture,
					 bool is_crosspart_update)
{
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;

	if (relinfo->ri_FdwRoutine && transition_capture &&
		transition_capture->tcs_delete_old_table)
	{
		Assert(relinfo->ri_RootResultRelInfo);
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot collect transition tuples from child foreign tables")));
	}

	if ((trigdesc && trigdesc->trig_delete_after_row) ||
		(transition_capture && transition_capture->tcs_delete_old_table))
	{
		TupleTableSlot *slot = ExecGetTriggerOldSlot(estate, relinfo);

		Assert(HeapTupleIsValid(fdw_trigtuple) ^ ItemPointerIsValid(tupleid));
		if (fdw_trigtuple == NULL)
			GetTupleForTrigger(estate,
							   NULL,
							   relinfo,
							   tupleid,
							   LockTupleExclusive,
							   slot,
							   false,
							   NULL,
							   NULL,
							   NULL);
		else
			ExecForceStoreHeapTuple(fdw_trigtuple, slot, false);

		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
							  TRIGGER_EVENT_DELETE,
							  true, slot, NULL, NIL, NULL,
							  transition_capture,
							  is_crosspart_update);
	}
}

bool
ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
					 HeapTuple trigtuple)
{
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
	TupleTableSlot *slot = ExecGetTriggerOldSlot(estate, relinfo);
	TriggerData LocTriggerData = {0};
	int			i;

	LocTriggerData.type = T_TriggerData;
	LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
		TRIGGER_EVENT_ROW |
		TRIGGER_EVENT_INSTEAD;
	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;

	ExecForceStoreHeapTuple(trigtuple, slot, false);

	for (i = 0; i < trigdesc->numtriggers; i++)
	{
		HeapTuple	rettuple;
		Trigger    *trigger = &trigdesc->triggers[i];

		if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
								  TRIGGER_TYPE_ROW,
								  TRIGGER_TYPE_INSTEAD,
								  TRIGGER_TYPE_DELETE))
			continue;
		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
							NULL, slot, NULL))
			continue;

		LocTriggerData.tg_trigslot = slot;
		LocTriggerData.tg_trigtuple = trigtuple;
		LocTriggerData.tg_trigger = trigger;
		rettuple = ExecCallTriggerFunc(&LocTriggerData,
									   i,
									   relinfo->ri_TrigFunctions,
									   relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(estate));
		if (rettuple == NULL)
			return false;		/* Delete was suppressed */
		if (rettuple != trigtuple)
			heap_freetuple(rettuple);
	}
	return true;
}

void
ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
{
	TriggerDesc *trigdesc;
	int			i;
	TriggerData LocTriggerData = {0};
	Bitmapset  *updatedCols;

	trigdesc = relinfo->ri_TrigDesc;

	if (trigdesc == NULL)
		return;
	if (!trigdesc->trig_update_before_statement)
		return;

	/* no-op if we already fired BS triggers in this context */
	if (before_stmt_triggers_fired(RelationGetRelid(relinfo->ri_RelationDesc),
								   CMD_UPDATE))
		return;

	/* statement-level triggers operate on the parent table */
	Assert(relinfo->ri_RootResultRelInfo == NULL);

	updatedCols = ExecGetAllUpdatedCols(relinfo, estate);

	LocTriggerData.type = T_TriggerData;
	LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
		TRIGGER_EVENT_BEFORE;
	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
	LocTriggerData.tg_updatedcols = updatedCols;
	for (i = 0; i < trigdesc->numtriggers; i++)
	{
		Trigger    *trigger = &trigdesc->triggers[i];
		HeapTuple	newtuple;

		if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
								  TRIGGER_TYPE_STATEMENT,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_UPDATE))
			continue;
		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
							updatedCols, NULL, NULL))
			continue;

		LocTriggerData.tg_trigger = trigger;
		newtuple = ExecCallTriggerFunc(&LocTriggerData,
									   i,
									   relinfo->ri_TrigFunctions,
									   relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(estate));

		if (newtuple)
			ereport(ERROR,
					(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
					 errmsg("BEFORE STATEMENT trigger cannot return a value")));
	}
}

void
ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
					 TransitionCaptureState *transition_capture)
{
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;

	/* statement-level triggers operate on the parent table */
	Assert(relinfo->ri_RootResultRelInfo == NULL);

	if (trigdesc && trigdesc->trig_update_after_statement)
		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
							  TRIGGER_EVENT_UPDATE,
							  false, NULL, NULL, NIL,
							  ExecGetAllUpdatedCols(relinfo, estate),
							  transition_capture,
							  false);
}

bool
ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
					 ResultRelInfo *relinfo,
					 ItemPointer tupleid,
					 HeapTuple fdw_trigtuple,
					 TupleTableSlot *newslot,
					 TM_Result *tmresult,
					 TM_FailureData *tmfd,
					 bool is_merge_update)
{
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
	TupleTableSlot *oldslot = ExecGetTriggerOldSlot(estate, relinfo);
	HeapTuple	newtuple = NULL;
	HeapTuple	trigtuple;
	bool		should_free_trig = false;
	bool		should_free_new = false;
	TriggerData LocTriggerData = {0};
	int			i;
	Bitmapset  *updatedCols;
	LockTupleMode lockmode;

	/* Determine lock mode to use */
	lockmode = ExecUpdateLockMode(estate, relinfo);

	Assert(HeapTupleIsValid(fdw_trigtuple) ^ ItemPointerIsValid(tupleid));
	if (fdw_trigtuple == NULL)
	{
		TupleTableSlot *epqslot_candidate = NULL;

		/*
		 * Get a copy of the on-disk tuple we are planning to update.  In
		 * general, if the tuple has been concurrently updated, we should
		 * recheck it using EPQ.  However, if this is a MERGE UPDATE action,
		 * we skip this EPQ recheck and leave it to the caller (it must do
		 * additional rechecking, and might end up executing a different
		 * action entirely).
		 */
		if (!GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
								lockmode, oldslot, !is_merge_update,
								&epqslot_candidate, tmresult, tmfd))
			return false;		/* cancel the update action */

		/*
		 * In READ COMMITTED isolation level it's possible that target tuple
		 * was changed due to concurrent update.  In that case we have a raw
		 * subplan output tuple in epqslot_candidate, and need to form a new
		 * insertable tuple using ExecGetUpdateNewTuple to replace the one we
		 * received in newslot.  Neither we nor our callers have any further
		 * interest in the passed-in tuple, so it's okay to overwrite newslot
		 * with the newer data.
		 */
		if (epqslot_candidate != NULL)
		{
			TupleTableSlot *epqslot_clean;

			epqslot_clean = ExecGetUpdateNewTuple(relinfo, epqslot_candidate,
												  oldslot);

			/*
			 * Typically, the caller's newslot was also generated by
			 * ExecGetUpdateNewTuple, so that epqslot_clean will be the same
			 * slot and copying is not needed.  But do the right thing if it
			 * isn't.
			 */
			if (unlikely(newslot != epqslot_clean))
				ExecCopySlot(newslot, epqslot_clean);

			/*
			 * At this point newslot contains a virtual tuple that may
			 * reference some fields of oldslot's tuple in some disk buffer.
			 * If that tuple is in a different page than the original target
			 * tuple, then our only pin on that buffer is oldslot's, and we're
			 * about to release it.  Hence we'd better materialize newslot to
			 * ensure it doesn't contain references into an unpinned buffer.
			 * (We'd materialize it below anyway, but too late for safety.)
			 */
			ExecMaterializeSlot(newslot);
		}

		/*
		 * Here we convert oldslot to a materialized slot holding trigtuple.
		 * Neither slot passed to the triggers will hold any buffer pin.
		 */
		trigtuple = ExecFetchSlotHeapTuple(oldslot, true, &should_free_trig);
	}
	else
	{
		/* Put the FDW-supplied tuple into oldslot to unify the cases */
		ExecForceStoreHeapTuple(fdw_trigtuple, oldslot, false);
		trigtuple = fdw_trigtuple;
	}

	LocTriggerData.type = T_TriggerData;
	LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
		TRIGGER_EVENT_ROW |
		TRIGGER_EVENT_BEFORE;
	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
	updatedCols = ExecGetAllUpdatedCols(relinfo, estate);
	LocTriggerData.tg_updatedcols = updatedCols;
	for (i = 0; i < trigdesc->numtriggers; i++)
	{
		Trigger    *trigger = &trigdesc->triggers[i];
		HeapTuple	oldtuple;

		if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
								  TRIGGER_TYPE_ROW,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_UPDATE))
			continue;
		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
							updatedCols, oldslot, newslot))
			continue;

		if (!newtuple)
			newtuple = ExecFetchSlotHeapTuple(newslot, true, &should_free_new);

		LocTriggerData.tg_trigslot = oldslot;
		LocTriggerData.tg_trigtuple = trigtuple;
		LocTriggerData.tg_newtuple = oldtuple = newtuple;
		LocTriggerData.tg_newslot = newslot;
		LocTriggerData.tg_trigger = trigger;
		newtuple = ExecCallTriggerFunc(&LocTriggerData,
									   i,
									   relinfo->ri_TrigFunctions,
									   relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(estate));

		if (newtuple == NULL)
		{
			if (should_free_trig)
				heap_freetuple(trigtuple);
			if (should_free_new)
				heap_freetuple(oldtuple);
			return false;		/* "do nothing" */
		}
		else if (newtuple != oldtuple)
		{
			newtuple = check_modified_virtual_generated(RelationGetDescr(relinfo->ri_RelationDesc), newtuple);

			ExecForceStoreHeapTuple(newtuple, newslot, false);

			/*
			 * If the tuple returned by the trigger / being stored, is the old
			 * row version, and the heap tuple passed to the trigger was
			 * allocated locally, materialize the slot. Otherwise we might
			 * free it while still referenced by the slot.
			 */
			if (should_free_trig && newtuple == trigtuple)
				ExecMaterializeSlot(newslot);

			if (should_free_new)
				heap_freetuple(oldtuple);

			/* signal tuple should be re-fetched if used */
			newtuple = NULL;
		}
	}
	if (should_free_trig)
		heap_freetuple(trigtuple);

	return true;
}

/*
 * Note: 'src_partinfo' and 'dst_partinfo', when non-NULL, refer to the source
 * and destination partitions, respectively, of a cross-partition update of
 * the root partitioned table mentioned in the query, given by 'relinfo'.
 * 'tupleid' in that case refers to the ctid of the "old" tuple in the source
 * partition, and 'newslot' contains the "new" tuple in the destination
 * partition.  This interface allows to support the requirements of
 * ExecCrossPartitionUpdateForeignKey(); is_crosspart_update must be true in
 * that case.
 */
void
ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
					 ResultRelInfo *src_partinfo,
					 ResultRelInfo *dst_partinfo,
					 ItemPointer tupleid,
					 HeapTuple fdw_trigtuple,
					 TupleTableSlot *newslot,
					 List *recheckIndexes,
					 TransitionCaptureState *transition_capture,
					 bool is_crosspart_update)
{
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;

	if (relinfo->ri_FdwRoutine && transition_capture &&
		(transition_capture->tcs_update_old_table ||
		 transition_capture->tcs_update_new_table))
	{
		Assert(relinfo->ri_RootResultRelInfo);
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot collect transition tuples from child foreign tables")));
	}

	if ((trigdesc && trigdesc->trig_update_after_row) ||
		(transition_capture &&
		 (transition_capture->tcs_update_old_table ||
		  transition_capture->tcs_update_new_table)))
	{
		/*
		 * Note: if the UPDATE is converted into a DELETE+INSERT as part of
		 * update-partition-key operation, then this function is also called
		 * separately for DELETE and INSERT to capture transition table rows.
		 * In such case, either old tuple or new tuple can be NULL.
		 */
		TupleTableSlot *oldslot;
		ResultRelInfo *tupsrc;

		Assert((src_partinfo != NULL && dst_partinfo != NULL) ||
			   !is_crosspart_update);

		tupsrc = src_partinfo ? src_partinfo : relinfo;
		oldslot = ExecGetTriggerOldSlot(estate, tupsrc);

		if (fdw_trigtuple == NULL && ItemPointerIsValid(tupleid))
			GetTupleForTrigger(estate,
							   NULL,
							   tupsrc,
							   tupleid,
							   LockTupleExclusive,
							   oldslot,
							   false,
							   NULL,
							   NULL,
							   NULL);
		else if (fdw_trigtuple != NULL)
			ExecForceStoreHeapTuple(fdw_trigtuple, oldslot, false);
		else
			ExecClearTuple(oldslot);

		AfterTriggerSaveEvent(estate, relinfo,
							  src_partinfo, dst_partinfo,
							  TRIGGER_EVENT_UPDATE,
							  true,
							  oldslot, newslot, recheckIndexes,
							  ExecGetAllUpdatedCols(relinfo, estate),
							  transition_capture,
							  is_crosspart_update);
	}
}

bool
ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
					 HeapTuple trigtuple, TupleTableSlot *newslot)
{
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
	TupleTableSlot *oldslot = ExecGetTriggerOldSlot(estate, relinfo);
	HeapTuple	newtuple = NULL;
	bool		should_free;
	TriggerData LocTriggerData = {0};
	int			i;

	LocTriggerData.type = T_TriggerData;
	LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
		TRIGGER_EVENT_ROW |
		TRIGGER_EVENT_INSTEAD;
	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;

	ExecForceStoreHeapTuple(trigtuple, oldslot, false);

	for (i = 0; i < trigdesc->numtriggers; i++)
	{
		Trigger    *trigger = &trigdesc->triggers[i];
		HeapTuple	oldtuple;

		if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
								  TRIGGER_TYPE_ROW,
								  TRIGGER_TYPE_INSTEAD,
								  TRIGGER_TYPE_UPDATE))
			continue;
		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
							NULL, oldslot, newslot))
			continue;

		if (!newtuple)
			newtuple = ExecFetchSlotHeapTuple(newslot, true, &should_free);

		LocTriggerData.tg_trigslot = oldslot;
		LocTriggerData.tg_trigtuple = trigtuple;
		LocTriggerData.tg_newslot = newslot;
		LocTriggerData.tg_newtuple = oldtuple = newtuple;

		LocTriggerData.tg_trigger = trigger;
		newtuple = ExecCallTriggerFunc(&LocTriggerData,
									   i,
									   relinfo->ri_TrigFunctions,
									   relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(estate));
		if (newtuple == NULL)
		{
			return false;		/* "do nothing" */
		}
		else if (newtuple != oldtuple)
		{
			ExecForceStoreHeapTuple(newtuple, newslot, false);

			if (should_free)
				heap_freetuple(oldtuple);

			/* signal tuple should be re-fetched if used */
			newtuple = NULL;
		}
	}

	return true;
}

void
ExecBSTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
{
	TriggerDesc *trigdesc;
	int			i;
	TriggerData LocTriggerData = {0};

	trigdesc = relinfo->ri_TrigDesc;

	if (trigdesc == NULL)
		return;
	if (!trigdesc->trig_truncate_before_statement)
		return;

	LocTriggerData.type = T_TriggerData;
	LocTriggerData.tg_event = TRIGGER_EVENT_TRUNCATE |
		TRIGGER_EVENT_BEFORE;
	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;

	for (i = 0; i < trigdesc->numtriggers; i++)
	{
		Trigger    *trigger = &trigdesc->triggers[i];
		HeapTuple	newtuple;

		if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
								  TRIGGER_TYPE_STATEMENT,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_TRUNCATE))
			continue;
		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
							NULL, NULL, NULL))
			continue;

		LocTriggerData.tg_trigger = trigger;
		newtuple = ExecCallTriggerFunc(&LocTriggerData,
									   i,
									   relinfo->ri_TrigFunctions,
									   relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(estate));

		if (newtuple)
			ereport(ERROR,
					(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
					 errmsg("BEFORE STATEMENT trigger cannot return a value")));
	}
}

void
ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
{
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;

	if (trigdesc && trigdesc->trig_truncate_after_statement)
		AfterTriggerSaveEvent(estate, relinfo,
							  NULL, NULL,
							  TRIGGER_EVENT_TRUNCATE,
							  false, NULL, NULL, NIL, NULL, NULL,
							  false);
}


/*
 * Fetch tuple into "oldslot", dealing with locking and EPQ if necessary
 */
static bool
GetTupleForTrigger(EState *estate,
				   EPQState *epqstate,
				   ResultRelInfo *relinfo,
				   ItemPointer tid,
				   LockTupleMode lockmode,
				   TupleTableSlot *oldslot,
				   bool do_epq_recheck,
				   TupleTableSlot **epqslot,
				   TM_Result *tmresultp,
				   TM_FailureData *tmfdp)
{
	Relation	relation = relinfo->ri_RelationDesc;

	if (epqslot != NULL)
	{
		TM_Result	test;
		TM_FailureData tmfd;
		int			lockflags = 0;

		*epqslot = NULL;

		/* caller must pass an epqstate if EvalPlanQual is possible */
		Assert(epqstate != NULL);

		/*
		 * lock tuple for update
		 */
		if (!IsolationUsesXactSnapshot())
			lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;
		test = table_tuple_lock(relation, tid, estate->es_snapshot, oldslot,
								estate->es_output_cid,
								lockmode, LockWaitBlock,
								lockflags,
								&tmfd);

		/* Let the caller know about the status of this operation */
		if (tmresultp)
			*tmresultp = test;
		if (tmfdp)
			*tmfdp = tmfd;

		switch (test)
		{
			case TM_SelfModified:

				/*
				 * The target tuple was already updated or deleted by the
				 * current command, or by a later command in the current
				 * transaction.  We ignore the tuple in the former case, and
				 * throw error in the latter case, for the same reasons
				 * enumerated in ExecUpdate and ExecDelete in
				 * nodeModifyTable.c.
				 */
				if (tmfd.cmax != estate->es_output_cid)
					ereport(ERROR,
							(errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
							 errmsg("tuple to be updated was already modified by an operation triggered by the current command"),
							 errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));

				/* treat it as deleted; do not process */
				return false;

			case TM_Ok:
				if (tmfd.traversed)
				{
					/*
					 * Recheck the tuple using EPQ, if requested.  Otherwise,
					 * just return that it was concurrently updated.
					 */
					if (do_epq_recheck)
					{
						*epqslot = EvalPlanQual(epqstate,
												relation,
												relinfo->ri_RangeTableIndex,
												oldslot);

						/*
						 * If PlanQual failed for updated tuple - we must not
						 * process this tuple!
						 */
						if (TupIsNull(*epqslot))
						{
							*epqslot = NULL;
							return false;
						}
					}
					else
					{
						if (tmresultp)
							*tmresultp = TM_Updated;
						return false;
					}
				}
				break;

			case TM_Updated:
				if (IsolationUsesXactSnapshot())
					ereport(ERROR,
							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
							 errmsg("could not serialize access due to concurrent update")));
				elog(ERROR, "unexpected table_tuple_lock status: %u", test);
				break;

			case TM_Deleted:
				if (IsolationUsesXactSnapshot())
					ereport(ERROR,
							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
							 errmsg("could not serialize access due to concurrent delete")));
				/* tuple was deleted */
				return false;

			case TM_Invisible:
				elog(ERROR, "attempted to lock invisible tuple");
				break;

			default:
				elog(ERROR, "unrecognized table_tuple_lock status: %u", test);
				return false;	/* keep compiler quiet */
		}
	}
	else
	{
		/*
		 * We expect the tuple to be present, thus very simple error handling
		 * suffices.
		 */
		if (!table_tuple_fetch_row_version(relation, tid, SnapshotAny,
										   oldslot))
			elog(ERROR, "failed to fetch tuple for trigger");
	}

	return true;
}

/*
 * Is trigger enabled to fire?
 */
static bool
TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
			   Trigger *trigger, TriggerEvent event,
			   Bitmapset *modifiedCols,
			   TupleTableSlot *oldslot, TupleTableSlot *newslot)
{
	/* Check replication-role-dependent enable state */
	if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
	{
		if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
			trigger->tgenabled == TRIGGER_DISABLED)
			return false;
	}
	else						/* ORIGIN or LOCAL role */
	{
		if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
			trigger->tgenabled == TRIGGER_DISABLED)
			return false;
	}

	/*
	 * Check for column-specific trigger (only possible for UPDATE, and in
	 * fact we *must* ignore tgattr for other event types)
	 */
	if (trigger->tgnattr > 0 && TRIGGER_FIRED_BY_UPDATE(event))
	{
		int			i;
		bool		modified;

		modified = false;
		for (i = 0; i < trigger->tgnattr; i++)
		{
			if (bms_is_member(trigger->tgattr[i] - FirstLowInvalidHeapAttributeNumber,
							  modifiedCols))
			{
				modified = true;
				break;
			}
		}
		if (!modified)
			return false;
	}

	/* Check for WHEN clause */
	if (trigger->tgqual)
	{
		ExprState **predicate;
		ExprContext *econtext;
		MemoryContext oldContext;
		int			i;

		Assert(estate != NULL);

		/*
		 * trigger is an element of relinfo->ri_TrigDesc->triggers[]; find the
		 * matching element of relinfo->ri_TrigWhenExprs[]
		 */
		i = trigger - relinfo->ri_TrigDesc->triggers;
		predicate = &relinfo->ri_TrigWhenExprs[i];

		/*
		 * If first time through for this WHEN expression, build expression
		 * nodetrees for it.  Keep them in the per-query memory context so
		 * they'll survive throughout the query.
		 */
		if (*predicate == NULL)
		{
			Node	   *tgqual;

			oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
			tgqual = stringToNode(trigger->tgqual);
			tgqual = expand_generated_columns_in_expr(tgqual, relinfo->ri_RelationDesc, PRS2_OLD_VARNO);
			tgqual = expand_generated_columns_in_expr(tgqual, relinfo->ri_RelationDesc, PRS2_NEW_VARNO);
			/* Change references to OLD and NEW to INNER_VAR and OUTER_VAR */
			ChangeVarNodes(tgqual, PRS2_OLD_VARNO, INNER_VAR, 0);
			ChangeVarNodes(tgqual, PRS2_NEW_VARNO, OUTER_VAR, 0);
			/* ExecPrepareQual wants implicit-AND form */
			tgqual = (Node *) make_ands_implicit((Expr *) tgqual);
			*predicate = ExecPrepareQual((List *) tgqual, estate);
			MemoryContextSwitchTo(oldContext);
		}

		/*
		 * We will use the EState's per-tuple context for evaluating WHEN
		 * expressions (creating it if it's not already there).
		 */
		econtext = GetPerTupleExprContext(estate);

		/*
		 * Finally evaluate the expression, making the old and/or new tuples
		 * available as INNER_VAR/OUTER_VAR respectively.
		 */
		econtext->ecxt_innertuple = oldslot;
		econtext->ecxt_outertuple = newslot;
		if (!ExecQual(*predicate, econtext))
			return false;
	}

	return true;
}


/* ----------
 * After-trigger stuff
 *
 * The AfterTriggersData struct holds data about pending AFTER trigger events
 * during the current transaction tree.  (BEFORE triggers are fired
 * immediately so we don't need any persistent state about them.)  The struct
 * and most of its subsidiary data are kept in TopTransactionContext; however
 * some data that can be discarded sooner appears in the CurTransactionContext
 * of the relevant subtransaction.  Also, the individual event records are
 * kept in a separate sub-context of TopTransactionContext.  This is done
 * mainly so that it's easy to tell from a memory context dump how much space
 * is being eaten by trigger events.
 *
 * Because the list of pending events can grow large, we go to some
 * considerable effort to minimize per-event memory consumption.  The event
 * records are grouped into chunks and common data for similar events in the
 * same chunk is only stored once.
 *
 * XXX We need to be able to save the per-event data in a file if it grows too
 * large.
 * ----------
 */

/* Per-trigger SET CONSTRAINT status */
typedef struct SetConstraintTriggerData
{
	Oid			sct_tgoid;
	bool		sct_tgisdeferred;
} SetConstraintTriggerData;

typedef struct SetConstraintTriggerData *SetConstraintTrigger;

/*
 * SET CONSTRAINT intra-transaction status.
 *
 * We make this a single palloc'd object so it can be copied and freed easily.
 *
 * all_isset and all_isdeferred are used to keep track
 * of SET CONSTRAINTS ALL {DEFERRED, IMMEDIATE}.
 *
 * trigstates[] stores per-trigger tgisdeferred settings.
 */
typedef struct SetConstraintStateData
{
	bool		all_isset;
	bool		all_isdeferred;
	int			numstates;		/* number of trigstates[] entries in use */
	int			numalloc;		/* allocated size of trigstates[] */
	SetConstraintTriggerData trigstates[FLEXIBLE_ARRAY_MEMBER];
} SetConstraintStateData;

typedef SetConstraintStateData *SetConstraintState;


/*
 * Per-trigger-event data
 *
 * The actual per-event data, AfterTriggerEventData, includes DONE/IN_PROGRESS
 * status bits, up to two tuple CTIDs, and optionally two OIDs of partitions.
 * Each event record also has an associated AfterTriggerSharedData that is
 * shared across all instances of similar events within a "chunk".
 *
 * For row-level triggers, we arrange not to waste storage on unneeded ctid
 * fields.  Updates of regular tables use two; inserts and deletes of regular
 * tables use one; foreign tables always use zero and save the tuple(s) to a
 * tuplestore.  AFTER_TRIGGER_FDW_FETCH directs AfterTriggerExecute() to
 * retrieve a fresh tuple or pair of tuples from that tuplestore, while
 * AFTER_TRIGGER_FDW_REUSE directs it to use the most-recently-retrieved
 * tuple(s).  This permits storing tuples once regardless of the number of
 * row-level triggers on a foreign table.
 *
 * When updates on partitioned tables cause rows to move between partitions,
 * the OIDs of both partitions are stored too, so that the tuples can be
 * fetched; such entries are marked AFTER_TRIGGER_CP_UPDATE (for "cross-
 * partition update").
 *
 * Note that we need triggers on foreign tables to be fired in exactly the
 * order they were queued, so that the tuples come out of the tuplestore in
 * the right order.  To ensure that, we forbid deferrable (constraint)
 * triggers on foreign tables.  This also ensures that such triggers do not
 * get deferred into outer trigger query levels, meaning that it's okay to
 * destroy the tuplestore at the end of the query level.
 *
 * Statement-level triggers always bear AFTER_TRIGGER_1CTID, though they
 * require no ctid field.  We lack the flag bit space to neatly represent that
 * distinct case, and it seems unlikely to be worth much trouble.
 *
 * Note: ats_firing_id is initially zero and is set to something else when
 * AFTER_TRIGGER_IN_PROGRESS is set.  It indicates which trigger firing
 * cycle the trigger will be fired in (or was fired in, if DONE is set).
 * Although this is mutable state, we can keep it in AfterTriggerSharedData
 * because all instances of the same type of event in a given event list will
 * be fired at the same time, if they were queued between the same firing
 * cycles.  So we need only ensure that ats_firing_id is zero when attaching
 * a new event to an existing AfterTriggerSharedData record.
 */
typedef uint32 TriggerFlags;

#define AFTER_TRIGGER_OFFSET			0x07FFFFFF	/* must be low-order bits */
#define AFTER_TRIGGER_DONE				0x80000000
#define AFTER_TRIGGER_IN_PROGRESS		0x40000000
/* bits describing the size and tuple sources of this event */
#define AFTER_TRIGGER_FDW_REUSE			0x00000000
#define AFTER_TRIGGER_FDW_FETCH			0x20000000
#define AFTER_TRIGGER_1CTID				0x10000000
#define AFTER_TRIGGER_2CTID				0x30000000
#define AFTER_TRIGGER_CP_UPDATE			0x08000000
#define AFTER_TRIGGER_TUP_BITS			0x38000000
typedef struct AfterTriggerSharedData *AfterTriggerShared;

typedef struct AfterTriggerSharedData
{
	TriggerEvent ats_event;		/* event type indicator, see trigger.h */
	Oid			ats_tgoid;		/* the trigger's ID */
	Oid			ats_relid;		/* the relation it's on */
	Oid			ats_rolid;		/* role to execute the trigger */
	CommandId	ats_firing_id;	/* ID for firing cycle */
	struct AfterTriggersTableData *ats_table;	/* transition table access */
	Bitmapset  *ats_modifiedcols;	/* modified columns */
} AfterTriggerSharedData;

typedef struct AfterTriggerEventData *AfterTriggerEvent;

typedef struct AfterTriggerEventData
{
	TriggerFlags ate_flags;		/* status bits and offset to shared data */
	ItemPointerData ate_ctid1;	/* inserted, deleted, or old updated tuple */
	ItemPointerData ate_ctid2;	/* new updated tuple */

	/*
	 * During a cross-partition update of a partitioned table, we also store
	 * the OIDs of source and destination partitions that are needed to fetch
	 * the old (ctid1) and the new tuple (ctid2) from, respectively.
	 */
	Oid			ate_src_part;
	Oid			ate_dst_part;
} AfterTriggerEventData;

/* AfterTriggerEventData, minus ate_src_part, ate_dst_part */
typedef struct AfterTriggerEventDataNoOids
{
	TriggerFlags ate_flags;
	ItemPointerData ate_ctid1;
	ItemPointerData ate_ctid2;
}			AfterTriggerEventDataNoOids;

/* AfterTriggerEventData, minus ate_*_part and ate_ctid2 */
typedef struct AfterTriggerEventDataOneCtid
{
	TriggerFlags ate_flags;		/* status bits and offset to shared data */
	ItemPointerData ate_ctid1;	/* inserted, deleted, or old updated tuple */
}			AfterTriggerEventDataOneCtid;

/* AfterTriggerEventData, minus ate_*_part, ate_ctid1 and ate_ctid2 */
typedef struct AfterTriggerEventDataZeroCtids
{
	TriggerFlags ate_flags;		/* status bits and offset to shared data */
}			AfterTriggerEventDataZeroCtids;

#define SizeofTriggerEvent(evt) \
	(((evt)->ate_flags & AFTER_TRIGGER_TUP_BITS) == AFTER_TRIGGER_CP_UPDATE ? \
	 sizeof(AfterTriggerEventData) : \
	 (((evt)->ate_flags & AFTER_TRIGGER_TUP_BITS) == AFTER_TRIGGER_2CTID ? \
	  sizeof(AfterTriggerEventDataNoOids) : \
	  (((evt)->ate_flags & AFTER_TRIGGER_TUP_BITS) == AFTER_TRIGGER_1CTID ? \
	   sizeof(AfterTriggerEventDataOneCtid) : \
	   sizeof(AfterTriggerEventDataZeroCtids))))

#define GetTriggerSharedData(evt) \
	((AfterTriggerShared) ((char *) (evt) + ((evt)->ate_flags & AFTER_TRIGGER_OFFSET)))

/*
 * To avoid palloc overhead, we keep trigger events in arrays in successively-
 * larger chunks (a slightly more sophisticated version of an expansible
 * array).  The space between CHUNK_DATA_START and freeptr is occupied by
 * AfterTriggerEventData records; the space between endfree and endptr is
 * occupied by AfterTriggerSharedData records.
 */
typedef struct AfterTriggerEventChunk
{
	struct AfterTriggerEventChunk *next;	/* list link */
	char	   *freeptr;		/* start of free space in chunk */
	char	   *endfree;		/* end of free space in chunk */
	char	   *endptr;			/* end of chunk */
	/* event data follows here */
} AfterTriggerEventChunk;

#define CHUNK_DATA_START(cptr) ((char *) (cptr) + MAXALIGN(sizeof(AfterTriggerEventChunk)))

/* A list of events */
typedef struct AfterTriggerEventList
{
	AfterTriggerEventChunk *head;
	AfterTriggerEventChunk *tail;
	char	   *tailfree;		/* freeptr of tail chunk */
} AfterTriggerEventList;

/* Macros to help in iterating over a list of events */
#define for_each_chunk(cptr, evtlist) \
	for (cptr = (evtlist).head; cptr != NULL; cptr = cptr->next)
#define for_each_event(eptr, cptr) \
	for (eptr = (AfterTriggerEvent) CHUNK_DATA_START(cptr); \
		 (char *) eptr < (cptr)->freeptr; \
		 eptr = (AfterTriggerEvent) (((char *) eptr) + SizeofTriggerEvent(eptr)))
/* Use this if no special per-chunk processing is needed */
#define for_each_event_chunk(eptr, cptr, evtlist) \
	for_each_chunk(cptr, evtlist) for_each_event(eptr, cptr)

/* Macros for iterating from a start point that might not be list start */
#define for_each_chunk_from(cptr) \
	for (; cptr != NULL; cptr = cptr->next)
#define for_each_event_from(eptr, cptr) \
	for (; \
		 (char *) eptr < (cptr)->freeptr; \
		 eptr = (AfterTriggerEvent) (((char *) eptr) + SizeofTriggerEvent(eptr)))


/*
 * All per-transaction data for the AFTER TRIGGERS module.
 *
 * AfterTriggersData has the following fields:
 *
 * firing_counter is incremented for each call of afterTriggerInvokeEvents.
 * We mark firable events with the current firing cycle's ID so that we can
 * tell which ones to work on.  This ensures sane behavior if a trigger
 * function chooses to do SET CONSTRAINTS: the inner SET CONSTRAINTS will
 * only fire those events that weren't already scheduled for firing.
 *
 * state keeps track of the transaction-local effects of SET CONSTRAINTS.
 * This is saved and restored across failed subtransactions.
 *
 * events is the current list of deferred events.  This is global across
 * all subtransactions of the current transaction.  In a subtransaction
 * abort, we know that the events added by the subtransaction are at the
 * end of the list, so it is relatively easy to discard them.  The event
 * list chunks themselves are stored in event_cxt.
 *
 * query_depth is the current depth of nested AfterTriggerBeginQuery calls
 * (-1 when the stack is empty).
 *
 * query_stack[query_depth] is the per-query-level data, including these fields:
 *
 * events is a list of AFTER trigger events queued by the current query.
 * None of these are valid until the matching AfterTriggerEndQuery call
 * occurs.  At that point we fire immediate-mode triggers, and append any
 * deferred events to the main events list.
 *
 * fdw_tuplestore is a tuplestore containing the foreign-table tuples
 * needed by events queued by the current query.  (Note: we use just one
 * tuplestore even though more than one foreign table might be involved.
 * This is okay because tuplestores don't really care what's in the tuples
 * they store; but it's possible that someday it'd break.)
 *
 * tables is a List of AfterTriggersTableData structs for target tables
 * of the current query (see below).
 *
 * maxquerydepth is just the allocated length of query_stack.
 *
 * trans_stack holds per-subtransaction data, including these fields:
 *
 * state is NULL or a pointer to a saved copy of the SET CONSTRAINTS
 * state data.  Each subtransaction level that modifies that state first
 * saves a copy, which we use to restore the state if we abort.
 *
 * events is a copy of the events head/tail pointers,
 * which we use to restore those values during subtransaction abort.
 *
 * query_depth is the subtransaction-start-time value of query_depth,
 * which we similarly use to clean up at subtransaction abort.
 *
 * firing_counter is the subtransaction-start-time value of firing_counter.
 * We use this to recognize which deferred triggers were fired (or marked
 * for firing) within an aborted subtransaction.
 *
 * We use GetCurrentTransactionNestLevel() to determine the correct array
 * index in trans_stack.  maxtransdepth is the number of allocated entries in
 * trans_stack.  (By not keeping our own stack pointer, we can avoid trouble
 * in cases where errors during subxact abort cause multiple invocations
 * of AfterTriggerEndSubXact() at the same nesting depth.)
 *
 * We create an AfterTriggersTableData struct for each target table of the
 * current query, and each operation mode (INSERT/UPDATE/DELETE), that has
 * either transition tables or statement-level triggers.  This is used to
 * hold the relevant transition tables, as well as info tracking whether
 * we already queued the statement triggers.  (We use that info to prevent
 * firing the same statement triggers more than once per statement, or really
 * once per transition table set.)  These structs, along with the transition
 * table tuplestores, live in the (sub)transaction's CurTransactionContext.
 * That's sufficient lifespan because we don't allow transition tables to be
 * used by deferrable triggers, so they only need to survive until
 * AfterTriggerEndQuery.
 */
typedef struct AfterTriggersQueryData AfterTriggersQueryData;
typedef struct AfterTriggersTransData AfterTriggersTransData;
typedef struct AfterTriggersTableData AfterTriggersTableData;

typedef struct AfterTriggersData
{
	CommandId	firing_counter; /* next firing ID to assign */
	SetConstraintState state;	/* the active S C state */
	AfterTriggerEventList events;	/* deferred-event list */
	MemoryContext event_cxt;	/* memory context for events, if any */

	/* per-query-level data: */
	AfterTriggersQueryData *query_stack;	/* array of structs shown below */
	int			query_depth;	/* current index in above array */
	int			maxquerydepth;	/* allocated len of above array */

	/* per-subtransaction-level data: */
	AfterTriggersTransData *trans_stack;	/* array of structs shown below */
	int			maxtransdepth;	/* allocated len of above array */
} AfterTriggersData;

struct AfterTriggersQueryData
{
	AfterTriggerEventList events;	/* events pending from this query */
	Tuplestorestate *fdw_tuplestore;	/* foreign tuples for said events */
	List	   *tables;			/* list of AfterTriggersTableData, see below */
};

struct AfterTriggersTransData
{
	/* these fields are just for resetting at subtrans abort: */
	SetConstraintState state;	/* saved S C state, or NULL if not yet saved */
	AfterTriggerEventList events;	/* saved list pointer */
	int			query_depth;	/* saved query_depth */
	CommandId	firing_counter; /* saved firing_counter */
};

struct AfterTriggersTableData
{
	/* relid + cmdType form the lookup key for these structs: */
	Oid			relid;			/* target table's OID */
	CmdType		cmdType;		/* event type, CMD_INSERT/UPDATE/DELETE */
	bool		closed;			/* true when no longer OK to add tuples */
	bool		before_trig_done;	/* did we already queue BS triggers? */
	bool		after_trig_done;	/* did we already queue AS triggers? */
	AfterTriggerEventList after_trig_events;	/* if so, saved list pointer */

	/* "old" transition table for UPDATE/DELETE, if any */
	Tuplestorestate *old_tuplestore;
	/* "new" transition table for INSERT/UPDATE, if any */
	Tuplestorestate *new_tuplestore;

	TupleTableSlot *storeslot;	/* for converting to tuplestore's format */
};

static AfterTriggersData afterTriggers;

static void AfterTriggerExecute(EState *estate,
								AfterTriggerEvent event,
								ResultRelInfo *relInfo,
								ResultRelInfo *src_relInfo,
								ResultRelInfo *dst_relInfo,
								TriggerDesc *trigdesc,
								FmgrInfo *finfo,
								Instrumentation *instr,
								MemoryContext per_tuple_context,
								TupleTableSlot *trig_tuple_slot1,
								TupleTableSlot *trig_tuple_slot2);
static AfterTriggersTableData *GetAfterTriggersTableData(Oid relid,
														 CmdType cmdType);
static TupleTableSlot *GetAfterTriggersStoreSlot(AfterTriggersTableData *table,
												 TupleDesc tupdesc);
static Tuplestorestate *GetAfterTriggersTransitionTable(int event,
														TupleTableSlot *oldslot,
														TupleTableSlot *newslot,
														TransitionCaptureState *transition_capture);
static void TransitionTableAddTuple(EState *estate,
									int event,
									TransitionCaptureState *transition_capture,
									ResultRelInfo *relinfo,
									TupleTableSlot *slot,
									TupleTableSlot *original_insert_tuple,
									Tuplestorestate *tuplestore);
static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs);
static SetConstraintState SetConstraintStateCreate(int numalloc);
static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate);
static SetConstraintState SetConstraintStateAddItem(SetConstraintState state,
													Oid tgoid, bool tgisdeferred);
static void cancel_prior_stmt_triggers(Oid relid, CmdType cmdType, int tgevent);


/*
 * Get the FDW tuplestore for the current trigger query level, creating it
 * if necessary.
 */
static Tuplestorestate *
GetCurrentFDWTuplestore(void)
{
	Tuplestorestate *ret;

	ret = afterTriggers.query_stack[afterTriggers.query_depth].fdw_tuplestore;
	if (ret == NULL)
	{
		MemoryContext oldcxt;
		ResourceOwner saveResourceOwner;

		/*
		 * Make the tuplestore valid until end of subtransaction.  We really
		 * only need it until AfterTriggerEndQuery().
		 */
		oldcxt = MemoryContextSwitchTo(CurTransactionContext);
		saveResourceOwner = CurrentResourceOwner;
		CurrentResourceOwner = CurTransactionResourceOwner;

		ret = tuplestore_begin_heap(false, false, work_mem);

		CurrentResourceOwner = saveResourceOwner;
		MemoryContextSwitchTo(oldcxt);

		afterTriggers.query_stack[afterTriggers.query_depth].fdw_tuplestore = ret;
	}

	return ret;
}

/* ----------
 * afterTriggerCheckState()
 *
 *	Returns true if the trigger event is actually in state DEFERRED.
 * ----------
 */
static bool
afterTriggerCheckState(AfterTriggerShared evtshared)
{
	Oid			tgoid = evtshared->ats_tgoid;
	SetConstraintState state = afterTriggers.state;
	int			i;

	/*
	 * For not-deferrable triggers (i.e. normal AFTER ROW triggers and
	 * constraints declared NOT DEFERRABLE), the state is always false.
	 */
	if ((evtshared->ats_event & AFTER_TRIGGER_DEFERRABLE) == 0)
		return false;

	/*
	 * If constraint state exists, SET CONSTRAINTS might have been executed
	 * either for this trigger or for all triggers.
	 */
	if (state != NULL)
	{
		/* Check for SET CONSTRAINTS for this specific trigger. */
		for (i = 0; i < state->numstates; i++)
		{
			if (state->trigstates[i].sct_tgoid == tgoid)
				return state->trigstates[i].sct_tgisdeferred;
		}

		/* Check for SET CONSTRAINTS ALL. */
		if (state->all_isset)
			return state->all_isdeferred;
	}

	/*
	 * Otherwise return the default state for the trigger.
	 */
	return ((evtshared->ats_event & AFTER_TRIGGER_INITDEFERRED) != 0);
}

/* ----------
 * afterTriggerCopyBitmap()
 *
 * Copy bitmap into AfterTriggerEvents memory context, which is where the after
 * trigger events are kept.
 * ----------
 */
static Bitmapset *
afterTriggerCopyBitmap(Bitmapset *src)
{
	Bitmapset  *dst;
	MemoryContext oldcxt;

	if (src == NULL)
		return NULL;

	oldcxt = MemoryContextSwitchTo(afterTriggers.event_cxt);

	dst = bms_copy(src);

	MemoryContextSwitchTo(oldcxt);

	return dst;
}

/* ----------
 * afterTriggerAddEvent()
 *
 *	Add a new trigger event to the specified queue.
 *	The passed-in event data is copied.
 * ----------
 */
static void
afterTriggerAddEvent(AfterTriggerEventList *events,
					 AfterTriggerEvent event, AfterTriggerShared evtshared)
{
	Size		eventsize = SizeofTriggerEvent(event);
	Size		needed = eventsize + sizeof(AfterTriggerSharedData);
	AfterTriggerEventChunk *chunk;
	AfterTriggerShared newshared;
	AfterTriggerEvent newevent;

	/*
	 * If empty list or not enough room in the tail chunk, make a new chunk.
	 * We assume here that a new shared record will always be needed.
	 */
	chunk = events->tail;
	if (chunk == NULL ||
		chunk->endfree - chunk->freeptr < needed)
	{
		Size		chunksize;

		/* Create event context if we didn't already */
		if (afterTriggers.event_cxt == NULL)
			afterTriggers.event_cxt =
				AllocSetContextCreate(TopTransactionContext,
									  "AfterTriggerEvents",
									  ALLOCSET_DEFAULT_SIZES);

		/*
		 * Chunk size starts at 1KB and is allowed to increase up to 1MB.
		 * These numbers are fairly arbitrary, though there is a hard limit at
		 * AFTER_TRIGGER_OFFSET; else we couldn't link event records to their
		 * shared records using the available space in ate_flags.  Another
		 * constraint is that if the chunk size gets too huge, the search loop
		 * below would get slow given a (not too common) usage pattern with
		 * many distinct event types in a chunk.  Therefore, we double the
		 * preceding chunk size only if there weren't too many shared records
		 * in the preceding chunk; otherwise we halve it.  This gives us some
		 * ability to adapt to the actual usage pattern of the current query
		 * while still having large chunk sizes in typical usage.  All chunk
		 * sizes used should be MAXALIGN multiples, to ensure that the shared
		 * records will be aligned safely.
		 */
#define MIN_CHUNK_SIZE 1024
#define MAX_CHUNK_SIZE (1024*1024)

#if MAX_CHUNK_SIZE > (AFTER_TRIGGER_OFFSET+1)
#error MAX_CHUNK_SIZE must not exceed AFTER_TRIGGER_OFFSET
#endif

		if (chunk == NULL)
			chunksize = MIN_CHUNK_SIZE;
		else
		{
			/* preceding chunk size... */
			chunksize = chunk->endptr - (char *) chunk;
			/* check number of shared records in preceding chunk */
			if ((chunk->endptr - chunk->endfree) <=
				(100 * sizeof(AfterTriggerSharedData)))
				chunksize *= 2; /* okay, double it */
			else
				chunksize /= 2; /* too many shared records */
			chunksize = Min(chunksize, MAX_CHUNK_SIZE);
		}
		chunk = MemoryContextAlloc(afterTriggers.event_cxt, chunksize);
		chunk->next = NULL;
		chunk->freeptr = CHUNK_DATA_START(chunk);
		chunk->endptr = chunk->endfree = (char *) chunk + chunksize;
		Assert(chunk->endfree - chunk->freeptr >= needed);

		if (events->tail == NULL)
		{
			Assert(events->head == NULL);
			events->head = chunk;
		}
		else
			events->tail->next = chunk;
		events->tail = chunk;
		/* events->tailfree is now out of sync, but we'll fix it below */
	}

	/*
	 * Try to locate a matching shared-data record already in the chunk. If
	 * none, make a new one. The search begins with the most recently added
	 * record, since newer ones are most likely to match.
	 */
	for (newshared = (AfterTriggerShared) chunk->endfree;
		 (char *) newshared < chunk->endptr;
		 newshared++)
	{
		/* compare fields roughly by probability of them being different */
		if (newshared->ats_tgoid == evtshared->ats_tgoid &&
			newshared->ats_event == evtshared->ats_event &&
			newshared->ats_firing_id == 0 &&
			newshared->ats_table == evtshared->ats_table &&
			newshared->ats_relid == evtshared->ats_relid &&
			newshared->ats_rolid == evtshared->ats_rolid &&
			bms_equal(newshared->ats_modifiedcols,
					  evtshared->ats_modifiedcols))
			break;
	}
	if ((char *) newshared >= chunk->endptr)
	{
		newshared = ((AfterTriggerShared) chunk->endfree) - 1;
		*newshared = *evtshared;
		/* now we must make a suitably-long-lived copy of the bitmap */
		newshared->ats_modifiedcols = afterTriggerCopyBitmap(evtshared->ats_modifiedcols);
		newshared->ats_firing_id = 0;	/* just to be sure */
		chunk->endfree = (char *) newshared;
	}

	/* Insert the data */
	newevent = (AfterTriggerEvent) chunk->freeptr;
	memcpy(newevent, event, eventsize);
	/* ... and link the new event to its shared record */
	newevent->ate_flags &= ~AFTER_TRIGGER_OFFSET;
	newevent->ate_flags |= (char *) newshared - (char *) newevent;

	chunk->freeptr += eventsize;
	events->tailfree = chunk->freeptr;
}

/* ----------
 * afterTriggerFreeEventList()
 *
 *	Free all the event storage in the given list.
 * ----------
 */
static void
afterTriggerFreeEventList(AfterTriggerEventList *events)
{
	AfterTriggerEventChunk *chunk;

	while ((chunk = events->head) != NULL)
	{
		events->head = chunk->next;
		pfree(chunk);
	}
	events->tail = NULL;
	events->tailfree = NULL;
}

/* ----------
 * afterTriggerRestoreEventList()
 *
 *	Restore an event list to its prior length, removing all the events
 *	added since it had the value old_events.
 * ----------
 */
static void
afterTriggerRestoreEventList(AfterTriggerEventList *events,
							 const AfterTriggerEventList *old_events)
{
	AfterTriggerEventChunk *chunk;
	AfterTriggerEventChunk *next_chunk;

	if (old_events->tail == NULL)
	{
		/* restoring to a completely empty state, so free everything */
		afterTriggerFreeEventList(events);
	}
	else
	{
		*events = *old_events;
		/* free any chunks after the last one we want to keep */
		for (chunk = events->tail->next; chunk != NULL; chunk = next_chunk)
		{
			next_chunk = chunk->next;
			pfree(chunk);
		}
		/* and clean up the tail chunk to be the right length */
		events->tail->next = NULL;
		events->tail->freeptr = events->tailfree;

		/*
		 * We don't make any effort to remove now-unused shared data records.
		 * They might still be useful, anyway.
		 */
	}
}

/* ----------
 * afterTriggerDeleteHeadEventChunk()
 *
 *	Remove the first chunk of events from the query level's event list.
 *	Keep any event list pointers elsewhere in the query level's data
 *	structures in sync.
 * ----------
 */
static void
afterTriggerDeleteHeadEventChunk(AfterTriggersQueryData *qs)
{
	AfterTriggerEventChunk *target = qs->events.head;
	ListCell   *lc;

	Assert(target && target->next);

	/*
	 * First, update any pointers in the per-table data, so that they won't be
	 * dangling.  Resetting obsoleted pointers to NULL will make
	 * cancel_prior_stmt_triggers start from the list head, which is fine.
	 */
	foreach(lc, qs->tables)
	{
		AfterTriggersTableData *table = (AfterTriggersTableData *) lfirst(lc);

		if (table->after_trig_done &&
			table->after_trig_events.tail == target)
		{
			table->after_trig_events.head = NULL;
			table->after_trig_events.tail = NULL;
			table->after_trig_events.tailfree = NULL;
		}
	}

	/* Now we can flush the head chunk */
	qs->events.head = target->next;
	pfree(target);
}


/* ----------
 * AfterTriggerExecute()
 *
 *	Fetch the required tuples back from the heap and fire one
 *	single trigger function.
 *
 *	Frequently, this will be fired many times in a row for triggers of
 *	a single relation.  Therefore, we cache the open relation and provide
 *	fmgr lookup cache space at the caller level.  (For triggers fired at
 *	the end of a query, we can even piggyback on the executor's state.)
 *
 *	When fired for a cross-partition update of a partitioned table, the old
 *	tuple is fetched using 'src_relInfo' (the source leaf partition) and
 *	the new tuple using 'dst_relInfo' (the destination leaf partition), though
 *	both are converted into the root partitioned table's format before passing
 *	to the trigger function.
 *
 *	event: event currently being fired.
 *	relInfo: result relation for event.
 *	src_relInfo: source partition of a cross-partition update
 *	dst_relInfo: its destination partition
 *	trigdesc: working copy of rel's trigger info.
 *	finfo: array of fmgr lookup cache entries (one per trigger in trigdesc).
 *	instr: array of EXPLAIN ANALYZE instrumentation nodes (one per trigger),
 *		or NULL if no instrumentation is wanted.
 *	per_tuple_context: memory context to call trigger function in.
 *	trig_tuple_slot1: scratch slot for tg_trigtuple (foreign tables only)
 *	trig_tuple_slot2: scratch slot for tg_newtuple (foreign tables only)
 * ----------
 */
static void
AfterTriggerExecute(EState *estate,
					AfterTriggerEvent event,
					ResultRelInfo *relInfo,
					ResultRelInfo *src_relInfo,
					ResultRelInfo *dst_relInfo,
					TriggerDesc *trigdesc,
					FmgrInfo *finfo, Instrumentation *instr,
					MemoryContext per_tuple_context,
					TupleTableSlot *trig_tuple_slot1,
					TupleTableSlot *trig_tuple_slot2)
{
	Relation	rel = relInfo->ri_RelationDesc;
	Relation	src_rel = src_relInfo->ri_RelationDesc;
	Relation	dst_rel = dst_relInfo->ri_RelationDesc;
	AfterTriggerShared evtshared = GetTriggerSharedData(event);
	Oid			tgoid = evtshared->ats_tgoid;
	TriggerData LocTriggerData = {0};
	Oid			save_rolid;
	int			save_sec_context;
	HeapTuple	rettuple;
	int			tgindx;
	bool		should_free_trig = false;
	bool		should_free_new = false;

	/*
	 * Locate trigger in trigdesc.  It might not be present, and in fact the
	 * trigdesc could be NULL, if the trigger was dropped since the event was
	 * queued.  In that case, silently do nothing.
	 */
	if (trigdesc == NULL)
		return;
	for (tgindx = 0; tgindx < trigdesc->numtriggers; tgindx++)
	{
		if (trigdesc->triggers[tgindx].tgoid == tgoid)
		{
			LocTriggerData.tg_trigger = &(trigdesc->triggers[tgindx]);
			break;
		}
	}
	if (LocTriggerData.tg_trigger == NULL)
		return;

	/*
	 * If doing EXPLAIN ANALYZE, start charging time to this trigger. We want
	 * to include time spent re-fetching tuples in the trigger cost.
	 */
	if (instr)
		InstrStartNode(instr + tgindx);

	/*
	 * Fetch the required tuple(s).
	 */
	switch (event->ate_flags & AFTER_TRIGGER_TUP_BITS)
	{
		case AFTER_TRIGGER_FDW_FETCH:
			{
				Tuplestorestate *fdw_tuplestore = GetCurrentFDWTuplestore();

				if (!tuplestore_gettupleslot(fdw_tuplestore, true, false,
											 trig_tuple_slot1))
					elog(ERROR, "failed to fetch tuple1 for AFTER trigger");

				if ((evtshared->ats_event & TRIGGER_EVENT_OPMASK) ==
					TRIGGER_EVENT_UPDATE &&
					!tuplestore_gettupleslot(fdw_tuplestore, true, false,
											 trig_tuple_slot2))
					elog(ERROR, "failed to fetch tuple2 for AFTER trigger");
			}
			/* fall through */
		case AFTER_TRIGGER_FDW_REUSE:

			/*
			 * Store tuple in the slot so that tg_trigtuple does not reference
			 * tuplestore memory.  (It is formally possible for the trigger
			 * function to queue trigger events that add to the same
			 * tuplestore, which can push other tuples out of memory.)  The
			 * distinction is academic, because we start with a minimal tuple
			 * that is stored as a heap tuple, constructed in different memory
			 * context, in the slot anyway.
			 */
			LocTriggerData.tg_trigslot = trig_tuple_slot1;
			LocTriggerData.tg_trigtuple =
				ExecFetchSlotHeapTuple(trig_tuple_slot1, true, &should_free_trig);

			if ((evtshared->ats_event & TRIGGER_EVENT_OPMASK) ==
				TRIGGER_EVENT_UPDATE)
			{
				LocTriggerData.tg_newslot = trig_tuple_slot2;
				LocTriggerData.tg_newtuple =
					ExecFetchSlotHeapTuple(trig_tuple_slot2, true, &should_free_new);
			}
			else
			{
				LocTriggerData.tg_newtuple = NULL;
			}
			break;

		default:
			if (ItemPointerIsValid(&(event->ate_ctid1)))
			{
				TupleTableSlot *src_slot = ExecGetTriggerOldSlot(estate,
																 src_relInfo);

				if (!table_tuple_fetch_row_version(src_rel,
												   &(event->ate_ctid1),
												   SnapshotAny,
												   src_slot))
					elog(ERROR, "failed to fetch tuple1 for AFTER trigger");

				/*
				 * Store the tuple fetched from the source partition into the
				 * target (root partitioned) table slot, converting if needed.
				 */
				if (src_relInfo != relInfo)
				{
					TupleConversionMap *map = ExecGetChildToRootMap(src_relInfo);

					LocTriggerData.tg_trigslot = ExecGetTriggerOldSlot(estate, relInfo);
					if (map)
					{
						execute_attr_map_slot(map->attrMap,
											  src_slot,
											  LocTriggerData.tg_trigslot);
					}
					else
						ExecCopySlot(LocTriggerData.tg_trigslot, src_slot);
				}
				else
					LocTriggerData.tg_trigslot = src_slot;
				LocTriggerData.tg_trigtuple =
					ExecFetchSlotHeapTuple(LocTriggerData.tg_trigslot, false, &should_free_trig);
			}
			else
			{
				LocTriggerData.tg_trigtuple = NULL;
			}

			/* don't touch ctid2 if not there */
			if (((event->ate_flags & AFTER_TRIGGER_TUP_BITS) == AFTER_TRIGGER_2CTID ||
				 (event->ate_flags & AFTER_TRIGGER_CP_UPDATE)) &&
				ItemPointerIsValid(&(event->ate_ctid2)))
			{
				TupleTableSlot *dst_slot = ExecGetTriggerNewSlot(estate,
																 dst_relInfo);

				if (!table_tuple_fetch_row_version(dst_rel,
												   &(event->ate_ctid2),
												   SnapshotAny,
												   dst_slot))
					elog(ERROR, "failed to fetch tuple2 for AFTER trigger");

				/*
				 * Store the tuple fetched from the destination partition into
				 * the target (root partitioned) table slot, converting if
				 * needed.
				 */
				if (dst_relInfo != relInfo)
				{
					TupleConversionMap *map = ExecGetChildToRootMap(dst_relInfo);

					LocTriggerData.tg_newslot = ExecGetTriggerNewSlot(estate, relInfo);
					if (map)
					{
						execute_attr_map_slot(map->attrMap,
											  dst_slot,
											  LocTriggerData.tg_newslot);
					}
					else
						ExecCopySlot(LocTriggerData.tg_newslot, dst_slot);
				}
				else
					LocTriggerData.tg_newslot = dst_slot;
				LocTriggerData.tg_newtuple =
					ExecFetchSlotHeapTuple(LocTriggerData.tg_newslot, false, &should_free_new);
			}
			else
			{
				LocTriggerData.tg_newtuple = NULL;
			}
	}

	/*
	 * Set up the tuplestore information to let the trigger have access to
	 * transition tables.  When we first make a transition table available to
	 * a trigger, mark it "closed" so that it cannot change anymore.  If any
	 * additional events of the same type get queued in the current trigger
	 * query level, they'll go into new transition tables.
	 */
	LocTriggerData.tg_oldtable = LocTriggerData.tg_newtable = NULL;
	if (evtshared->ats_table)
	{
		if (LocTriggerData.tg_trigger->tgoldtable)
		{
			LocTriggerData.tg_oldtable = evtshared->ats_table->old_tuplestore;
			evtshared->ats_table->closed = true;
		}

		if (LocTriggerData.tg_trigger->tgnewtable)
		{
			LocTriggerData.tg_newtable = evtshared->ats_table->new_tuplestore;
			evtshared->ats_table->closed = true;
		}
	}

	/*
	 * Setup the remaining trigger information
	 */
	LocTriggerData.type = T_TriggerData;
	LocTriggerData.tg_event =
		evtshared->ats_event & (TRIGGER_EVENT_OPMASK | TRIGGER_EVENT_ROW);
	LocTriggerData.tg_relation = rel;
	if (TRIGGER_FOR_UPDATE(LocTriggerData.tg_trigger->tgtype))
		LocTriggerData.tg_updatedcols = evtshared->ats_modifiedcols;

	MemoryContextReset(per_tuple_context);

	/*
	 * If necessary, become the role that was active when the trigger got
	 * queued.  Note that the role might have been dropped since the trigger
	 * was queued, but if that is a problem, we will get an error later.
	 * Checking here would still leave a race condition.
	 */
	GetUserIdAndSecContext(&save_rolid, &save_sec_context);
	if (save_rolid != evtshared->ats_rolid)
		SetUserIdAndSecContext(evtshared->ats_rolid,
							   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);

	/*
	 * Call the trigger and throw away any possibly returned updated tuple.
	 * (Don't let ExecCallTriggerFunc measure EXPLAIN time.)
	 */
	rettuple = ExecCallTriggerFunc(&LocTriggerData,
								   tgindx,
								   finfo,
								   NULL,
								   per_tuple_context);
	if (rettuple != NULL &&
		rettuple != LocTriggerData.tg_trigtuple &&
		rettuple != LocTriggerData.tg_newtuple)
		heap_freetuple(rettuple);

	/* Restore the current role if necessary */
	if (save_rolid != evtshared->ats_rolid)
		SetUserIdAndSecContext(save_rolid, save_sec_context);

	/*
	 * Release resources
	 */
	if (should_free_trig)
		heap_freetuple(LocTriggerData.tg_trigtuple);
	if (should_free_new)
		heap_freetuple(LocTriggerData.tg_newtuple);

	/* don't clear slots' contents if foreign table */
	if (trig_tuple_slot1 == NULL)
	{
		if (LocTriggerData.tg_trigslot)
			ExecClearTuple(LocTriggerData.tg_trigslot);
		if (LocTriggerData.tg_newslot)
			ExecClearTuple(LocTriggerData.tg_newslot);
	}

	/*
	 * If doing EXPLAIN ANALYZE, stop charging time to this trigger, and count
	 * one "tuple returned" (really the number of firings).
	 */
	if (instr)
		InstrStopNode(instr + tgindx, 1);
}


/*
 * afterTriggerMarkEvents()
 *
 *	Scan the given event list for not yet invoked events.  Mark the ones
 *	that can be invoked now with the current firing ID.
 *
 *	If move_list isn't NULL, events that are not to be invoked now are
 *	transferred to move_list.
 *
 *	When immediate_only is true, do not invoke currently-deferred triggers.
 *	(This will be false only at main transaction exit.)
 *
 *	Returns true if any invokable events were found.
 */
static bool
afterTriggerMarkEvents(AfterTriggerEventList *events,
					   AfterTriggerEventList *move_list,
					   bool immediate_only)
{
	bool		found = false;
	bool		deferred_found = false;
	AfterTriggerEvent event;
	AfterTriggerEventChunk *chunk;

	for_each_event_chunk(event, chunk, *events)
	{
		AfterTriggerShared evtshared = GetTriggerSharedData(event);
		bool		defer_it = false;

		if (!(event->ate_flags &
			  (AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS)))
		{
			/*
			 * This trigger hasn't been called or scheduled yet. Check if we
			 * should call it now.
			 */
			if (immediate_only && afterTriggerCheckState(evtshared))
			{
				defer_it = true;
			}
			else
			{
				/*
				 * Mark it as to be fired in this firing cycle.
				 */
				evtshared->ats_firing_id = afterTriggers.firing_counter;
				event->ate_flags |= AFTER_TRIGGER_IN_PROGRESS;
				found = true;
			}
		}

		/*
		 * If it's deferred, move it to move_list, if requested.
		 */
		if (defer_it && move_list != NULL)
		{
			deferred_found = true;
			/* add it to move_list */
			afterTriggerAddEvent(move_list, event, evtshared);
			/* mark original copy "done" so we don't do it again */
			event->ate_flags |= AFTER_TRIGGER_DONE;
		}
	}

	/*
	 * We could allow deferred triggers if, before the end of the
	 * security-restricted operation, we were to verify that a SET CONSTRAINTS
	 * ... IMMEDIATE has fired all such triggers.  For now, don't bother.
	 */
	if (deferred_found && InSecurityRestrictedOperation())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("cannot fire deferred trigger within security-restricted operation")));

	return found;
}

/*
 * afterTriggerInvokeEvents()
 *
 *	Scan the given event list for events that are marked as to be fired
 *	in the current firing cycle, and fire them.
 *
 *	If estate isn't NULL, we use its result relation info to avoid repeated
 *	openings and closing of trigger target relations.  If it is NULL, we
 *	make one locally to cache the info in case there are multiple trigger
 *	events per rel.
 *
 *	When delete_ok is true, it's safe to delete fully-processed events.
 *	(We are not very tense about that: we simply reset a chunk to be empty
 *	if all its events got fired.  The objective here is just to avoid useless
 *	rescanning of events when a trigger queues new events during transaction
 *	end, so it's not necessary to worry much about the case where only
 *	some events are fired.)
 *
 *	Returns true if no unfired events remain in the list (this allows us
 *	to avoid repeating afterTriggerMarkEvents).
 */
static bool
afterTriggerInvokeEvents(AfterTriggerEventList *events,
						 CommandId firing_id,
						 EState *estate,
						 bool delete_ok)
{
	bool		all_fired = true;
	AfterTriggerEventChunk *chunk;
	MemoryContext per_tuple_context;
	bool		local_estate = false;
	ResultRelInfo *rInfo = NULL;
	Relation	rel = NULL;
	TriggerDesc *trigdesc = NULL;
	FmgrInfo   *finfo = NULL;
	Instrumentation *instr = NULL;
	TupleTableSlot *slot1 = NULL,
			   *slot2 = NULL;

	/* Make a local EState if need be */
	if (estate == NULL)
	{
		estate = CreateExecutorState();
		local_estate = true;
	}

	/* Make a per-tuple memory context for trigger function calls */
	per_tuple_context =
		AllocSetContextCreate(CurrentMemoryContext,
							  "AfterTriggerTupleContext",
							  ALLOCSET_DEFAULT_SIZES);

	for_each_chunk(chunk, *events)
	{
		AfterTriggerEvent event;
		bool		all_fired_in_chunk = true;

		for_each_event(event, chunk)
		{
			AfterTriggerShared evtshared = GetTriggerSharedData(event);

			/*
			 * Is it one for me to fire?
			 */
			if ((event->ate_flags & AFTER_TRIGGER_IN_PROGRESS) &&
				evtshared->ats_firing_id == firing_id)
			{
				ResultRelInfo *src_rInfo,
						   *dst_rInfo;

				/*
				 * So let's fire it... but first, find the correct relation if
				 * this is not the same relation as before.
				 */
				if (rel == NULL || RelationGetRelid(rel) != evtshared->ats_relid)
				{
					rInfo = ExecGetTriggerResultRel(estate, evtshared->ats_relid,
													NULL);
					rel = rInfo->ri_RelationDesc;
					/* Catch calls with insufficient relcache refcounting */
					Assert(!RelationHasReferenceCountZero(rel));
					trigdesc = rInfo->ri_TrigDesc;
					/* caution: trigdesc could be NULL here */
					finfo = rInfo->ri_TrigFunctions;
					instr = rInfo->ri_TrigInstrument;
					if (slot1 != NULL)
					{
						ExecDropSingleTupleTableSlot(slot1);
						ExecDropSingleTupleTableSlot(slot2);
						slot1 = slot2 = NULL;
					}
					if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
					{
						slot1 = MakeSingleTupleTableSlot(rel->rd_att,
														 &TTSOpsMinimalTuple);
						slot2 = MakeSingleTupleTableSlot(rel->rd_att,
														 &TTSOpsMinimalTuple);
					}
				}

				/*
				 * Look up source and destination partition result rels of a
				 * cross-partition update event.
				 */
				if ((event->ate_flags & AFTER_TRIGGER_TUP_BITS) ==
					AFTER_TRIGGER_CP_UPDATE)
				{
					Assert(OidIsValid(event->ate_src_part) &&
						   OidIsValid(event->ate_dst_part));
					src_rInfo = ExecGetTriggerResultRel(estate,
														event->ate_src_part,
														rInfo);
					dst_rInfo = ExecGetTriggerResultRel(estate,
														event->ate_dst_part,
														rInfo);
				}
				else
					src_rInfo = dst_rInfo = rInfo;

				/*
				 * Fire it.  Note that the AFTER_TRIGGER_IN_PROGRESS flag is
				 * still set, so recursive examinations of the event list
				 * won't try to re-fire it.
				 */
				AfterTriggerExecute(estate, event, rInfo,
									src_rInfo, dst_rInfo,
									trigdesc, finfo, instr,
									per_tuple_context, slot1, slot2);

				/*
				 * Mark the event as done.
				 */
				event->ate_flags &= ~AFTER_TRIGGER_IN_PROGRESS;
				event->ate_flags |= AFTER_TRIGGER_DONE;
			}
			else if (!(event->ate_flags & AFTER_TRIGGER_DONE))
			{
				/* something remains to be done */
				all_fired = all_fired_in_chunk = false;
			}
		}

		/* Clear the chunk if delete_ok and nothing left of interest */
		if (delete_ok && all_fired_in_chunk)
		{
			chunk->freeptr = CHUNK_DATA_START(chunk);
			chunk->endfree = chunk->endptr;

			/*
			 * If it's last chunk, must sync event list's tailfree too.  Note
			 * that delete_ok must NOT be passed as true if there could be
			 * additional AfterTriggerEventList values pointing at this event
			 * list, since we'd fail to fix their copies of tailfree.
			 */
			if (chunk == events->tail)
				events->tailfree = chunk->freeptr;
		}
	}
	if (slot1 != NULL)
	{
		ExecDropSingleTupleTableSlot(slot1);
		ExecDropSingleTupleTableSlot(slot2);
	}

	/* Release working resources */
	MemoryContextDelete(per_tuple_context);

	if (local_estate)
	{
		ExecCloseResultRelations(estate);
		ExecResetTupleTable(estate->es_tupleTable, false);
		FreeExecutorState(estate);
	}

	return all_fired;
}


/*
 * GetAfterTriggersTableData
 *
 * Find or create an AfterTriggersTableData struct for the specified
 * trigger event (relation + operation type).  Ignore existing structs
 * marked "closed"; we don't want to put any additional tuples into them,
 * nor change their stmt-triggers-fired state.
 *
 * Note: the AfterTriggersTableData list is allocated in the current
 * (sub)transaction's CurTransactionContext.  This is OK because
 * we don't need it to live past AfterTriggerEndQuery.
 */
static AfterTriggersTableData *
GetAfterTriggersTableData(Oid relid, CmdType cmdType)
{
	AfterTriggersTableData *table;
	AfterTriggersQueryData *qs;
	MemoryContext oldcxt;
	ListCell   *lc;

	/* At this level, cmdType should not be, eg, CMD_MERGE */
	Assert(cmdType == CMD_INSERT ||
		   cmdType == CMD_UPDATE ||
		   cmdType == CMD_DELETE);

	/* Caller should have ensured query_depth is OK. */
	Assert(afterTriggers.query_depth >= 0 &&
		   afterTriggers.query_depth < afterTriggers.maxquerydepth);
	qs = &afterTriggers.query_stack[afterTriggers.query_depth];

	foreach(lc, qs->tables)
	{
		table = (AfterTriggersTableData *) lfirst(lc);
		if (table->relid == relid && table->cmdType == cmdType &&
			!table->closed)
			return table;
	}

	oldcxt = MemoryContextSwitchTo(CurTransactionContext);

	table = (AfterTriggersTableData *) palloc0(sizeof(AfterTriggersTableData));
	table->relid = relid;
	table->cmdType = cmdType;
	qs->tables = lappend(qs->tables, table);

	MemoryContextSwitchTo(oldcxt);

	return table;
}

/*
 * Returns a TupleTableSlot suitable for holding the tuples to be put
 * into AfterTriggersTableData's transition table tuplestores.
 */
static TupleTableSlot *
GetAfterTriggersStoreSlot(AfterTriggersTableData *table,
						  TupleDesc tupdesc)
{
	/* Create it if not already done. */
	if (!table->storeslot)
	{
		MemoryContext oldcxt;

		/*
		 * We need this slot only until AfterTriggerEndQuery, but making it
		 * last till end-of-subxact is good enough.  It'll be freed by
		 * AfterTriggerFreeQuery().  However, the passed-in tupdesc might have
		 * a different lifespan, so we'd better make a copy of that.
		 */
		oldcxt = MemoryContextSwitchTo(CurTransactionContext);
		tupdesc = CreateTupleDescCopy(tupdesc);
		table->storeslot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual);
		MemoryContextSwitchTo(oldcxt);
	}

	return table->storeslot;
}

/*
 * MakeTransitionCaptureState
 *
 * Make a TransitionCaptureState object for the given TriggerDesc, target
 * relation, and operation type.  The TCS object holds all the state needed
 * to decide whether to capture tuples in transition tables.
 *
 * If there are no triggers in 'trigdesc' that request relevant transition
 * tables, then return NULL.
 *
 * The resulting object can be passed to the ExecAR* functions.  When
 * dealing with child tables, the caller can set tcs_original_insert_tuple
 * to avoid having to reconstruct the original tuple in the root table's
 * format.
 *
 * Note that we copy the flags from a parent table into this struct (rather
 * than subsequently using the relation's TriggerDesc directly) so that we can
 * use it to control collection of transition tuples from child tables.
 *
 * Per SQL spec, all operations of the same kind (INSERT/UPDATE/DELETE)
 * on the same table during one query should share one transition table.
 * Therefore, the Tuplestores are owned by an AfterTriggersTableData struct
 * looked up using the table OID + CmdType, and are merely referenced by
 * the TransitionCaptureState objects we hand out to callers.
 */
TransitionCaptureState *
MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
{
	TransitionCaptureState *state;
	bool		need_old_upd,
				need_new_upd,
				need_old_del,
				need_new_ins;
	AfterTriggersTableData *ins_table;
	AfterTriggersTableData *upd_table;
	AfterTriggersTableData *del_table;
	MemoryContext oldcxt;
	ResourceOwner saveResourceOwner;

	if (trigdesc == NULL)
		return NULL;

	/* Detect which table(s) we need. */
	switch (cmdType)
	{
		case CMD_INSERT:
			need_old_upd = need_old_del = need_new_upd = false;
			need_new_ins = trigdesc->trig_insert_new_table;
			break;
		case CMD_UPDATE:
			need_old_upd = trigdesc->trig_update_old_table;
			need_new_upd = trigdesc->trig_update_new_table;
			need_old_del = need_new_ins = false;
			break;
		case CMD_DELETE:
			need_old_del = trigdesc->trig_delete_old_table;
			need_old_upd = need_new_upd = need_new_ins = false;
			break;
		case CMD_MERGE:
			need_old_upd = trigdesc->trig_update_old_table;
			need_new_upd = trigdesc->trig_update_new_table;
			need_old_del = trigdesc->trig_delete_old_table;
			need_new_ins = trigdesc->trig_insert_new_table;
			break;
		default:
			elog(ERROR, "unexpected CmdType: %d", (int) cmdType);
			/* keep compiler quiet */
			need_old_upd = need_new_upd = need_old_del = need_new_ins = false;
			break;
	}
	if (!need_old_upd && !need_new_upd && !need_new_ins && !need_old_del)
		return NULL;

	/* Check state, like AfterTriggerSaveEvent. */
	if (afterTriggers.query_depth < 0)
		elog(ERROR, "MakeTransitionCaptureState() called outside of query");

	/* Be sure we have enough space to record events at this query depth. */
	if (afterTriggers.query_depth >= afterTriggers.maxquerydepth)
		AfterTriggerEnlargeQueryState();

	/*
	 * Find or create AfterTriggersTableData struct(s) to hold the
	 * tuplestore(s).  If there's a matching struct but it's marked closed,
	 * ignore it; we need a newer one.
	 *
	 * Note: MERGE must use the same AfterTriggersTableData structs as INSERT,
	 * UPDATE, and DELETE, so that any MERGE'd tuples are added to the same
	 * tuplestores as tuples from any INSERT, UPDATE, or DELETE commands
	 * running in the same top-level command (e.g., in a writable CTE).
	 *
	 * Note: the AfterTriggersTableData list, as well as the tuplestores, are
	 * allocated in the current (sub)transaction's CurTransactionContext, and
	 * the tuplestores are managed by the (sub)transaction's resource owner.
	 * This is sufficient lifespan because we do not allow triggers using
	 * transition tables to be deferrable; they will be fired during
	 * AfterTriggerEndQuery, after which it's okay to delete the data.
	 */
	if (need_new_ins)
		ins_table = GetAfterTriggersTableData(relid, CMD_INSERT);
	else
		ins_table = NULL;

	if (need_old_upd || need_new_upd)
		upd_table = GetAfterTriggersTableData(relid, CMD_UPDATE);
	else
		upd_table = NULL;

	if (need_old_del)
		del_table = GetAfterTriggersTableData(relid, CMD_DELETE);
	else
		del_table = NULL;

	/* Now create required tuplestore(s), if we don't have them already. */
	oldcxt = MemoryContextSwitchTo(CurTransactionContext);
	saveResourceOwner = CurrentResourceOwner;
	CurrentResourceOwner = CurTransactionResourceOwner;

	if (need_old_upd && upd_table->old_tuplestore == NULL)
		upd_table->old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
	if (need_new_upd && upd_table->new_tuplestore == NULL)
		upd_table->new_tuplestore = tuplestore_begin_heap(false, false, work_mem);
	if (need_old_del && del_table->old_tuplestore == NULL)
		del_table->old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
	if (need_new_ins && ins_table->new_tuplestore == NULL)
		ins_table->new_tuplestore = tuplestore_begin_heap(false, false, work_mem);

	CurrentResourceOwner = saveResourceOwner;
	MemoryContextSwitchTo(oldcxt);

	/* Now build the TransitionCaptureState struct, in caller's context */
	state = (TransitionCaptureState *) palloc0(sizeof(TransitionCaptureState));
	state->tcs_delete_old_table = need_old_del;
	state->tcs_update_old_table = need_old_upd;
	state->tcs_update_new_table = need_new_upd;
	state->tcs_insert_new_table = need_new_ins;
	state->tcs_insert_private = ins_table;
	state->tcs_update_private = upd_table;
	state->tcs_delete_private = del_table;

	return state;
}


/* ----------
 * AfterTriggerBeginXact()
 *
 *	Called at transaction start (either BEGIN or implicit for single
 *	statement outside of transaction block).
 * ----------
 */
void
AfterTriggerBeginXact(void)
{
	/*
	 * Initialize after-trigger state structure to empty
	 */
	afterTriggers.firing_counter = (CommandId) 1;	/* mustn't be 0 */
	afterTriggers.query_depth = -1;

	/*
	 * Verify that there is no leftover state remaining.  If these assertions
	 * trip, it means that AfterTriggerEndXact wasn't called or didn't clean
	 * up properly.
	 */
	Assert(afterTriggers.state == NULL);
	Assert(afterTriggers.query_stack == NULL);
	Assert(afterTriggers.maxquerydepth == 0);
	Assert(afterTriggers.event_cxt == NULL);
	Assert(afterTriggers.events.head == NULL);
	Assert(afterTriggers.trans_stack == NULL);
	Assert(afterTriggers.maxtransdepth == 0);
}


/* ----------
 * AfterTriggerBeginQuery()
 *
 *	Called just before we start processing a single query within a
 *	transaction (or subtransaction).  Most of the real work gets deferred
 *	until somebody actually tries to queue a trigger event.
 * ----------
 */
void
AfterTriggerBeginQuery(void)
{
	/* Increase the query stack depth */
	afterTriggers.query_depth++;
}


/* ----------
 * AfterTriggerEndQuery()
 *
 *	Called after one query has been completely processed. At this time
 *	we invoke all AFTER IMMEDIATE trigger events queued by the query, and
 *	transfer deferred trigger events to the global deferred-trigger list.
 *
 *	Note that this must be called BEFORE closing down the executor
 *	with ExecutorEnd, because we make use of the EState's info about
 *	target relations.  Normally it is called from ExecutorFinish.
 * ----------
 */
void
AfterTriggerEndQuery(EState *estate)
{
	AfterTriggersQueryData *qs;

	/* Must be inside a query, too */
	Assert(afterTriggers.query_depth >= 0);

	/*
	 * If we never even got as far as initializing the event stack, there
	 * certainly won't be any events, so exit quickly.
	 */
	if (afterTriggers.query_depth >= afterTriggers.maxquerydepth)
	{
		afterTriggers.query_depth--;
		return;
	}

	/*
	 * Process all immediate-mode triggers queued by the query, and move the
	 * deferred ones to the main list of deferred events.
	 *
	 * Notice that we decide which ones will be fired, and put the deferred
	 * ones on the main list, before anything is actually fired.  This ensures
	 * reasonably sane behavior if a trigger function does SET CONSTRAINTS ...
	 * IMMEDIATE: all events we have decided to defer will be available for it
	 * to fire.
	 *
	 * We loop in case a trigger queues more events at the same query level.
	 * Ordinary trigger functions, including all PL/pgSQL trigger functions,
	 * will instead fire any triggers in a dedicated query level.  Foreign key
	 * enforcement triggers do add to the current query level, thanks to their
	 * passing fire_triggers = false to SPI_execute_snapshot().  Other
	 * C-language triggers might do likewise.
	 *
	 * If we find no firable events, we don't have to increment
	 * firing_counter.
	 */
	qs = &afterTriggers.query_stack[afterTriggers.query_depth];

	for (;;)
	{
		if (afterTriggerMarkEvents(&qs->events, &afterTriggers.events, true))
		{
			CommandId	firing_id = afterTriggers.firing_counter++;
			AfterTriggerEventChunk *oldtail = qs->events.tail;

			if (afterTriggerInvokeEvents(&qs->events, firing_id, estate, false))
				break;			/* all fired */

			/*
			 * Firing a trigger could result in query_stack being repalloc'd,
			 * so we must recalculate qs after each afterTriggerInvokeEvents
			 * call.  Furthermore, it's unsafe to pass delete_ok = true here,
			 * because that could cause afterTriggerInvokeEvents to try to
			 * access qs->events after the stack has been repalloc'd.
			 */
			qs = &afterTriggers.query_stack[afterTriggers.query_depth];

			/*
			 * We'll need to scan the events list again.  To reduce the cost
			 * of doing so, get rid of completely-fired chunks.  We know that
			 * all events were marked IN_PROGRESS or DONE at the conclusion of
			 * afterTriggerMarkEvents, so any still-interesting events must
			 * have been added after that, and so must be in the chunk that
			 * was then the tail chunk, or in later chunks.  So, zap all
			 * chunks before oldtail.  This is approximately the same set of
			 * events we would have gotten rid of by passing delete_ok = true.
			 */
			Assert(oldtail != NULL);
			while (qs->events.head != oldtail)
				afterTriggerDeleteHeadEventChunk(qs);
		}
		else
			break;
	}

	/* Release query-level-local storage, including tuplestores if any */
	AfterTriggerFreeQuery(&afterTriggers.query_stack[afterTriggers.query_depth]);

	afterTriggers.query_depth--;
}


/*
 * AfterTriggerFreeQuery
 *	Release subsidiary storage for a trigger query level.
 *	This includes closing down tuplestores.
 *	Note: it's important for this to be safe if interrupted by an error
 *	and then called again for the same query level.
 */
static void
AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
{
	Tuplestorestate *ts;
	List	   *tables;
	ListCell   *lc;

	/* Drop the trigger events */
	afterTriggerFreeEventList(&qs->events);

	/* Drop FDW tuplestore if any */
	ts = qs->fdw_tuplestore;
	qs->fdw_tuplestore = NULL;
	if (ts)
		tuplestore_end(ts);

	/* Release per-table subsidiary storage */
	tables = qs->tables;
	foreach(lc, tables)
	{
		AfterTriggersTableData *table = (AfterTriggersTableData *) lfirst(lc);

		ts = table->old_tuplestore;
		table->old_tuplestore = NULL;
		if (ts)
			tuplestore_end(ts);
		ts = table->new_tuplestore;
		table->new_tuplestore = NULL;
		if (ts)
			tuplestore_end(ts);
		if (table->storeslot)
		{
			TupleTableSlot *slot = table->storeslot;

			table->storeslot = NULL;
			ExecDropSingleTupleTableSlot(slot);
		}
	}

	/*
	 * Now free the AfterTriggersTableData structs and list cells.  Reset list
	 * pointer first; if list_free_deep somehow gets an error, better to leak
	 * that storage than have an infinite loop.
	 */
	qs->tables = NIL;
	list_free_deep(tables);
}


/* ----------
 * AfterTriggerFireDeferred()
 *
 *	Called just before the current transaction is committed. At this
 *	time we invoke all pending DEFERRED triggers.
 *
 *	It is possible for other modules to queue additional deferred triggers
 *	during pre-commit processing; therefore xact.c may have to call this
 *	multiple times.
 * ----------
 */
void
AfterTriggerFireDeferred(void)
{
	AfterTriggerEventList *events;
	bool		snap_pushed = false;

	/* Must not be inside a query */
	Assert(afterTriggers.query_depth == -1);

	/*
	 * If there are any triggers to fire, make sure we have set a snapshot for
	 * them to use.  (Since PortalRunUtility doesn't set a snap for COMMIT, we
	 * can't assume ActiveSnapshot is valid on entry.)
	 */
	events = &afterTriggers.events;
	if (events->head != NULL)
	{
		PushActiveSnapshot(GetTransactionSnapshot());
		snap_pushed = true;
	}

	/*
	 * Run all the remaining triggers.  Loop until they are all gone, in case
	 * some trigger queues more for us to do.
	 */
	while (afterTriggerMarkEvents(events, NULL, false))
	{
		CommandId	firing_id = afterTriggers.firing_counter++;

		if (afterTriggerInvokeEvents(events, firing_id, NULL, true))
			break;				/* all fired */
	}

	/*
	 * We don't bother freeing the event list, since it will go away anyway
	 * (and more efficiently than via pfree) in AfterTriggerEndXact.
	 */

	if (snap_pushed)
		PopActiveSnapshot();
}


/* ----------
 * AfterTriggerEndXact()
 *
 *	The current transaction is finishing.
 *
 *	Any unfired triggers are canceled so we simply throw
 *	away anything we know.
 *
 *	Note: it is possible for this to be called repeatedly in case of
 *	error during transaction abort; therefore, do not complain if
 *	already closed down.
 * ----------
 */
void
AfterTriggerEndXact(bool isCommit)
{
	/*
	 * Forget the pending-events list.
	 *
	 * Since all the info is in TopTransactionContext or children thereof, we
	 * don't really need to do anything to reclaim memory.  However, the
	 * pending-events list could be large, and so it's useful to discard it as
	 * soon as possible --- especially if we are aborting because we ran out
	 * of memory for the list!
	 */
	if (afterTriggers.event_cxt)
	{
		MemoryContextDelete(afterTriggers.event_cxt);
		afterTriggers.event_cxt = NULL;
		afterTriggers.events.head = NULL;
		afterTriggers.events.tail = NULL;
		afterTriggers.events.tailfree = NULL;
	}

	/*
	 * Forget any subtransaction state as well.  Since this can't be very
	 * large, we let the eventual reset of TopTransactionContext free the
	 * memory instead of doing it here.
	 */
	afterTriggers.trans_stack = NULL;
	afterTriggers.maxtransdepth = 0;


	/*
	 * Forget the query stack and constraint-related state information.  As
	 * with the subtransaction state information, we don't bother freeing the
	 * memory here.
	 */
	afterTriggers.query_stack = NULL;
	afterTriggers.maxquerydepth = 0;
	afterTriggers.state = NULL;

	/* No more afterTriggers manipulation until next transaction starts. */
	afterTriggers.query_depth = -1;
}

/*
 * AfterTriggerBeginSubXact()
 *
 *	Start a subtransaction.
 */
void
AfterTriggerBeginSubXact(void)
{
	int			my_level = GetCurrentTransactionNestLevel();

	/*
	 * Allocate more space in the trans_stack if needed.  (Note: because the
	 * minimum nest level of a subtransaction is 2, we waste the first couple
	 * entries of the array; not worth the notational effort to avoid it.)
	 */
	while (my_level >= afterTriggers.maxtransdepth)
	{
		if (afterTriggers.maxtransdepth == 0)
		{
			/* Arbitrarily initialize for max of 8 subtransaction levels */
			afterTriggers.trans_stack = (AfterTriggersTransData *)
				MemoryContextAlloc(TopTransactionContext,
								   8 * sizeof(AfterTriggersTransData));
			afterTriggers.maxtransdepth = 8;
		}
		else
		{
			/* repalloc will keep the stack in the same context */
			int			new_alloc = afterTriggers.maxtransdepth * 2;

			afterTriggers.trans_stack = (AfterTriggersTransData *)
				repalloc(afterTriggers.trans_stack,
						 new_alloc * sizeof(AfterTriggersTransData));
			afterTriggers.maxtransdepth = new_alloc;
		}
	}

	/*
	 * Push the current information into the stack.  The SET CONSTRAINTS state
	 * is not saved until/unless changed.  Likewise, we don't make a
	 * per-subtransaction event context until needed.
	 */
	afterTriggers.trans_stack[my_level].state = NULL;
	afterTriggers.trans_stack[my_level].events = afterTriggers.events;
	afterTriggers.trans_stack[my_level].query_depth = afterTriggers.query_depth;
	afterTriggers.trans_stack[my_level].firing_counter = afterTriggers.firing_counter;
}

/*
 * AfterTriggerEndSubXact()
 *
 *	The current subtransaction is ending.
 */
void
AfterTriggerEndSubXact(bool isCommit)
{
	int			my_level = GetCurrentTransactionNestLevel();
	SetConstraintState state;
	AfterTriggerEvent event;
	AfterTriggerEventChunk *chunk;
	CommandId	subxact_firing_id;

	/*
	 * Pop the prior state if needed.
	 */
	if (isCommit)
	{
		Assert(my_level < afterTriggers.maxtransdepth);
		/* If we saved a prior state, we don't need it anymore */
		state = afterTriggers.trans_stack[my_level].state;
		if (state != NULL)
			pfree(state);
		/* this avoids double pfree if error later: */
		afterTriggers.trans_stack[my_level].state = NULL;
		Assert(afterTriggers.query_depth ==
			   afterTriggers.trans_stack[my_level].query_depth);
	}
	else
	{
		/*
		 * Aborting.  It is possible subxact start failed before calling
		 * AfterTriggerBeginSubXact, in which case we mustn't risk touching
		 * trans_stack levels that aren't there.
		 */
		if (my_level >= afterTriggers.maxtransdepth)
			return;

		/*
		 * Release query-level storage for queries being aborted, and restore
		 * query_depth to its pre-subxact value.  This assumes that a
		 * subtransaction will not add events to query levels started in a
		 * earlier transaction state.
		 */
		while (afterTriggers.query_depth > afterTriggers.trans_stack[my_level].query_depth)
		{
			if (afterTriggers.query_depth < afterTriggers.maxquerydepth)
				AfterTriggerFreeQuery(&afterTriggers.query_stack[afterTriggers.query_depth]);
			afterTriggers.query_depth--;
		}
		Assert(afterTriggers.query_depth ==
			   afterTriggers.trans_stack[my_level].query_depth);

		/*
		 * Restore the global deferred-event list to its former length,
		 * discarding any events queued by the subxact.
		 */
		afterTriggerRestoreEventList(&afterTriggers.events,
									 &afterTriggers.trans_stack[my_level].events);

		/*
		 * Restore the trigger state.  If the saved state is NULL, then this
		 * subxact didn't save it, so it doesn't need restoring.
		 */
		state = afterTriggers.trans_stack[my_level].state;
		if (state != NULL)
		{
			pfree(afterTriggers.state);
			afterTriggers.state = state;
		}
		/* this avoids double pfree if error later: */
		afterTriggers.trans_stack[my_level].state = NULL;

		/*
		 * Scan for any remaining deferred events that were marked DONE or IN
		 * PROGRESS by this subxact or a child, and un-mark them. We can
		 * recognize such events because they have a firing ID greater than or
		 * equal to the firing_counter value we saved at subtransaction start.
		 * (This essentially assumes that the current subxact includes all
		 * subxacts started after it.)
		 */
		subxact_firing_id = afterTriggers.trans_stack[my_level].firing_counter;
		for_each_event_chunk(event, chunk, afterTriggers.events)
		{
			AfterTriggerShared evtshared = GetTriggerSharedData(event);

			if (event->ate_flags &
				(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS))
			{
				if (evtshared->ats_firing_id >= subxact_firing_id)
					event->ate_flags &=
						~(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS);
			}
		}
	}
}

/*
 * Get the transition table for the given event and depending on whether we are
 * processing the old or the new tuple.
 */
static Tuplestorestate *
GetAfterTriggersTransitionTable(int event,
								TupleTableSlot *oldslot,
								TupleTableSlot *newslot,
								TransitionCaptureState *transition_capture)
{
	Tuplestorestate *tuplestore = NULL;
	bool		delete_old_table = transition_capture->tcs_delete_old_table;
	bool		update_old_table = transition_capture->tcs_update_old_table;
	bool		update_new_table = transition_capture->tcs_update_new_table;
	bool		insert_new_table = transition_capture->tcs_insert_new_table;

	/*
	 * For INSERT events NEW should be non-NULL, for DELETE events OLD should
	 * be non-NULL, whereas for UPDATE events normally both OLD and NEW are
	 * non-NULL.  But for UPDATE events fired for capturing transition tuples
	 * during UPDATE partition-key row movement, OLD is NULL when the event is
	 * for a row being inserted, whereas NEW is NULL when the event is for a
	 * row being deleted.
	 */
	Assert(!(event == TRIGGER_EVENT_DELETE && delete_old_table &&
			 TupIsNull(oldslot)));
	Assert(!(event == TRIGGER_EVENT_INSERT && insert_new_table &&
			 TupIsNull(newslot)));

	if (!TupIsNull(oldslot))
	{
		Assert(TupIsNull(newslot));
		if (event == TRIGGER_EVENT_DELETE && delete_old_table)
			tuplestore = transition_capture->tcs_delete_private->old_tuplestore;
		else if (event == TRIGGER_EVENT_UPDATE && update_old_table)
			tuplestore = transition_capture->tcs_update_private->old_tuplestore;
	}
	else if (!TupIsNull(newslot))
	{
		Assert(TupIsNull(oldslot));
		if (event == TRIGGER_EVENT_INSERT && insert_new_table)
			tuplestore = transition_capture->tcs_insert_private->new_tuplestore;
		else if (event == TRIGGER_EVENT_UPDATE && update_new_table)
			tuplestore = transition_capture->tcs_update_private->new_tuplestore;
	}

	return tuplestore;
}

/*
 * Add the given heap tuple to the given tuplestore, applying the conversion
 * map if necessary.
 *
 * If original_insert_tuple is given, we can add that tuple without conversion.
 */
static void
TransitionTableAddTuple(EState *estate,
						int event,
						TransitionCaptureState *transition_capture,
						ResultRelInfo *relinfo,
						TupleTableSlot *slot,
						TupleTableSlot *original_insert_tuple,
						Tuplestorestate *tuplestore)
{
	TupleConversionMap *map;

	/*
	 * Nothing needs to be done if we don't have a tuplestore.
	 */
	if (tuplestore == NULL)
		return;

	if (original_insert_tuple)
		tuplestore_puttupleslot(tuplestore, original_insert_tuple);
	else if ((map = ExecGetChildToRootMap(relinfo)) != NULL)
	{
		AfterTriggersTableData *table;
		TupleTableSlot *storeslot;

		switch (event)
		{
			case TRIGGER_EVENT_INSERT:
				table = transition_capture->tcs_insert_private;
				break;
			case TRIGGER_EVENT_UPDATE:
				table = transition_capture->tcs_update_private;
				break;
			case TRIGGER_EVENT_DELETE:
				table = transition_capture->tcs_delete_private;
				break;
			default:
				elog(ERROR, "invalid after-trigger event code: %d", event);
				table = NULL;	/* keep compiler quiet */
				break;
		}

		storeslot = GetAfterTriggersStoreSlot(table, map->outdesc);
		execute_attr_map_slot(map->attrMap, slot, storeslot);
		tuplestore_puttupleslot(tuplestore, storeslot);
	}
	else
		tuplestore_puttupleslot(tuplestore, slot);
}

/* ----------
 * AfterTriggerEnlargeQueryState()
 *
 *	Prepare the necessary state so that we can record AFTER trigger events
 *	queued by a query.  It is allowed to have nested queries within a
 *	(sub)transaction, so we need to have separate state for each query
 *	nesting level.
 * ----------
 */
static void
AfterTriggerEnlargeQueryState(void)
{
	int			init_depth = afterTriggers.maxquerydepth;

	Assert(afterTriggers.query_depth >= afterTriggers.maxquerydepth);

	if (afterTriggers.maxquerydepth == 0)
	{
		int			new_alloc = Max(afterTriggers.query_depth + 1, 8);

		afterTriggers.query_stack = (AfterTriggersQueryData *)
			MemoryContextAlloc(TopTransactionContext,
							   new_alloc * sizeof(AfterTriggersQueryData));
		afterTriggers.maxquerydepth = new_alloc;
	}
	else
	{
		/* repalloc will keep the stack in the same context */
		int			old_alloc = afterTriggers.maxquerydepth;
		int			new_alloc = Max(afterTriggers.query_depth + 1,
									old_alloc * 2);

		afterTriggers.query_stack = (AfterTriggersQueryData *)
			repalloc(afterTriggers.query_stack,
					 new_alloc * sizeof(AfterTriggersQueryData));
		afterTriggers.maxquerydepth = new_alloc;
	}

	/* Initialize new array entries to empty */
	while (init_depth < afterTriggers.maxquerydepth)
	{
		AfterTriggersQueryData *qs = &afterTriggers.query_stack[init_depth];

		qs->events.head = NULL;
		qs->events.tail = NULL;
		qs->events.tailfree = NULL;
		qs->fdw_tuplestore = NULL;
		qs->tables = NIL;

		++init_depth;
	}
}

/*
 * Create an empty SetConstraintState with room for numalloc trigstates
 */
static SetConstraintState
SetConstraintStateCreate(int numalloc)
{
	SetConstraintState state;

	/* Behave sanely with numalloc == 0 */
	if (numalloc <= 0)
		numalloc = 1;

	/*
	 * We assume that zeroing will correctly initialize the state values.
	 */
	state = (SetConstraintState)
		MemoryContextAllocZero(TopTransactionContext,
							   offsetof(SetConstraintStateData, trigstates) +
							   numalloc * sizeof(SetConstraintTriggerData));

	state->numalloc = numalloc;

	return state;
}

/*
 * Copy a SetConstraintState
 */
static SetConstraintState
SetConstraintStateCopy(SetConstraintState origstate)
{
	SetConstraintState state;

	state = SetConstraintStateCreate(origstate->numstates);

	state->all_isset = origstate->all_isset;
	state->all_isdeferred = origstate->all_isdeferred;
	state->numstates = origstate->numstates;
	memcpy(state->trigstates, origstate->trigstates,
		   origstate->numstates * sizeof(SetConstraintTriggerData));

	return state;
}

/*
 * Add a per-trigger item to a SetConstraintState.  Returns possibly-changed
 * pointer to the state object (it will change if we have to repalloc).
 */
static SetConstraintState
SetConstraintStateAddItem(SetConstraintState state,
						  Oid tgoid, bool tgisdeferred)
{
	if (state->numstates >= state->numalloc)
	{
		int			newalloc = state->numalloc * 2;

		newalloc = Max(newalloc, 8);	/* in case original has size 0 */
		state = (SetConstraintState)
			repalloc(state,
					 offsetof(SetConstraintStateData, trigstates) +
					 newalloc * sizeof(SetConstraintTriggerData));
		state->numalloc = newalloc;
		Assert(state->numstates < state->numalloc);
	}

	state->trigstates[state->numstates].sct_tgoid = tgoid;
	state->trigstates[state->numstates].sct_tgisdeferred = tgisdeferred;
	state->numstates++;

	return state;
}

/* ----------
 * AfterTriggerSetState()
 *
 *	Execute the SET CONSTRAINTS ... utility command.
 * ----------
 */
void
AfterTriggerSetState(ConstraintsSetStmt *stmt)
{
	int			my_level = GetCurrentTransactionNestLevel();

	/* If we haven't already done so, initialize our state. */
	if (afterTriggers.state == NULL)
		afterTriggers.state = SetConstraintStateCreate(8);

	/*
	 * If in a subtransaction, and we didn't save the current state already,
	 * save it so it can be restored if the subtransaction aborts.
	 */
	if (my_level > 1 &&
		afterTriggers.trans_stack[my_level].state == NULL)
	{
		afterTriggers.trans_stack[my_level].state =
			SetConstraintStateCopy(afterTriggers.state);
	}

	/*
	 * Handle SET CONSTRAINTS ALL ...
	 */
	if (stmt->constraints == NIL)
	{
		/*
		 * Forget any previous SET CONSTRAINTS commands in this transaction.
		 */
		afterTriggers.state->numstates = 0;

		/*
		 * Set the per-transaction ALL state to known.
		 */
		afterTriggers.state->all_isset = true;
		afterTriggers.state->all_isdeferred = stmt->deferred;
	}
	else
	{
		Relation	conrel;
		Relation	tgrel;
		List	   *conoidlist = NIL;
		List	   *tgoidlist = NIL;
		ListCell   *lc;

		/*
		 * Handle SET CONSTRAINTS constraint-name [, ...]
		 *
		 * First, identify all the named constraints and make a list of their
		 * OIDs.  Since, unlike the SQL spec, we allow multiple constraints of
		 * the same name within a schema, the specifications are not
		 * necessarily unique.  Our strategy is to target all matching
		 * constraints within the first search-path schema that has any
		 * matches, but disregard matches in schemas beyond the first match.
		 * (This is a bit odd but it's the historical behavior.)
		 *
		 * A constraint in a partitioned table may have corresponding
		 * constraints in the partitions.  Grab those too.
		 */
		conrel = table_open(ConstraintRelationId, AccessShareLock);

		foreach(lc, stmt->constraints)
		{
			RangeVar   *constraint = lfirst(lc);
			bool		found;
			List	   *namespacelist;
			ListCell   *nslc;

			if (constraint->catalogname)
			{
				if (strcmp(constraint->catalogname, get_database_name(MyDatabaseId)) != 0)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
									constraint->catalogname, constraint->schemaname,
									constraint->relname)));
			}

			/*
			 * If we're given the schema name with the constraint, look only
			 * in that schema.  If given a bare constraint name, use the
			 * search path to find the first matching constraint.
			 */
			if (constraint->schemaname)
			{
				Oid			namespaceId = LookupExplicitNamespace(constraint->schemaname,
																  false);

				namespacelist = list_make1_oid(namespaceId);
			}
			else
			{
				namespacelist = fetch_search_path(true);
			}

			found = false;
			foreach(nslc, namespacelist)
			{
				Oid			namespaceId = lfirst_oid(nslc);
				SysScanDesc conscan;
				ScanKeyData skey[2];
				HeapTuple	tup;

				ScanKeyInit(&skey[0],
							Anum_pg_constraint_conname,
							BTEqualStrategyNumber, F_NAMEEQ,
							CStringGetDatum(constraint->relname));
				ScanKeyInit(&skey[1],
							Anum_pg_constraint_connamespace,
							BTEqualStrategyNumber, F_OIDEQ,
							ObjectIdGetDatum(namespaceId));

				conscan = systable_beginscan(conrel, ConstraintNameNspIndexId,
											 true, NULL, 2, skey);

				while (HeapTupleIsValid(tup = systable_getnext(conscan)))
				{
					Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup);

					if (con->condeferrable)
						conoidlist = lappend_oid(conoidlist, con->oid);
					else if (stmt->deferred)
						ereport(ERROR,
								(errcode(ERRCODE_WRONG_OBJECT_TYPE),
								 errmsg("constraint \"%s\" is not deferrable",
										constraint->relname)));
					found = true;
				}

				systable_endscan(conscan);

				/*
				 * Once we've found a matching constraint we do not search
				 * later parts of the search path.
				 */
				if (found)
					break;
			}

			list_free(namespacelist);

			/*
			 * Not found ?
			 */
			if (!found)
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_OBJECT),
						 errmsg("constraint \"%s\" does not exist",
								constraint->relname)));
		}

		/*
		 * Scan for any possible descendants of the constraints.  We append
		 * whatever we find to the same list that we're scanning; this has the
		 * effect that we create new scans for those, too, so if there are
		 * further descendents, we'll also catch them.
		 */
		foreach(lc, conoidlist)
		{
			Oid			parent = lfirst_oid(lc);
			ScanKeyData key;
			SysScanDesc scan;
			HeapTuple	tuple;

			ScanKeyInit(&key,
						Anum_pg_constraint_conparentid,
						BTEqualStrategyNumber, F_OIDEQ,
						ObjectIdGetDatum(parent));

			scan = systable_beginscan(conrel, ConstraintParentIndexId, true, NULL, 1, &key);

			while (HeapTupleIsValid(tuple = systable_getnext(scan)))
			{
				Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);

				conoidlist = lappend_oid(conoidlist, con->oid);
			}

			systable_endscan(scan);
		}

		table_close(conrel, AccessShareLock);

		/*
		 * Now, locate the trigger(s) implementing each of these constraints,
		 * and make a list of their OIDs.
		 */
		tgrel = table_open(TriggerRelationId, AccessShareLock);

		foreach(lc, conoidlist)
		{
			Oid			conoid = lfirst_oid(lc);
			ScanKeyData skey;
			SysScanDesc tgscan;
			HeapTuple	htup;

			ScanKeyInit(&skey,
						Anum_pg_trigger_tgconstraint,
						BTEqualStrategyNumber, F_OIDEQ,
						ObjectIdGetDatum(conoid));

			tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true,
										NULL, 1, &skey);

			while (HeapTupleIsValid(htup = systable_getnext(tgscan)))
			{
				Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);

				/*
				 * Silently skip triggers that are marked as non-deferrable in
				 * pg_trigger.  This is not an error condition, since a
				 * deferrable RI constraint may have some non-deferrable
				 * actions.
				 */
				if (pg_trigger->tgdeferrable)
					tgoidlist = lappend_oid(tgoidlist, pg_trigger->oid);
			}

			systable_endscan(tgscan);
		}

		table_close(tgrel, AccessShareLock);

		/*
		 * Now we can set the trigger states of individual triggers for this
		 * xact.
		 */
		foreach(lc, tgoidlist)
		{
			Oid			tgoid = lfirst_oid(lc);
			SetConstraintState state = afterTriggers.state;
			bool		found = false;
			int			i;

			for (i = 0; i < state->numstates; i++)
			{
				if (state->trigstates[i].sct_tgoid == tgoid)
				{
					state->trigstates[i].sct_tgisdeferred = stmt->deferred;
					found = true;
					break;
				}
			}
			if (!found)
			{
				afterTriggers.state =
					SetConstraintStateAddItem(state, tgoid, stmt->deferred);
			}
		}
	}

	/*
	 * SQL99 requires that when a constraint is set to IMMEDIATE, any deferred
	 * checks against that constraint must be made when the SET CONSTRAINTS
	 * command is executed -- i.e. the effects of the SET CONSTRAINTS command
	 * apply retroactively.  We've updated the constraints state, so scan the
	 * list of previously deferred events to fire any that have now become
	 * immediate.
	 *
	 * Obviously, if this was SET ... DEFERRED then it can't have converted
	 * any unfired events to immediate, so we need do nothing in that case.
	 */
	if (!stmt->deferred)
	{
		AfterTriggerEventList *events = &afterTriggers.events;
		bool		snapshot_set = false;

		while (afterTriggerMarkEvents(events, NULL, true))
		{
			CommandId	firing_id = afterTriggers.firing_counter++;

			/*
			 * Make sure a snapshot has been established in case trigger
			 * functions need one.  Note that we avoid setting a snapshot if
			 * we don't find at least one trigger that has to be fired now.
			 * This is so that BEGIN; SET CONSTRAINTS ...; SET TRANSACTION
			 * ISOLATION LEVEL SERIALIZABLE; ... works properly.  (If we are
			 * at the start of a transaction it's not possible for any trigger
			 * events to be queued yet.)
			 */
			if (!snapshot_set)
			{
				PushActiveSnapshot(GetTransactionSnapshot());
				snapshot_set = true;
			}

			/*
			 * We can delete fired events if we are at top transaction level,
			 * but we'd better not if inside a subtransaction, since the
			 * subtransaction could later get rolled back.
			 */
			if (afterTriggerInvokeEvents(events, firing_id, NULL,
										 !IsSubTransaction()))
				break;			/* all fired */
		}

		if (snapshot_set)
			PopActiveSnapshot();
	}
}

/* ----------
 * AfterTriggerPendingOnRel()
 *		Test to see if there are any pending after-trigger events for rel.
 *
 * This is used by TRUNCATE, CLUSTER, ALTER TABLE, etc to detect whether
 * it is unsafe to perform major surgery on a relation.  Note that only
 * local pending events are examined.  We assume that having exclusive lock
 * on a rel guarantees there are no unserviced events in other backends ---
 * but having a lock does not prevent there being such events in our own.
 *
 * In some scenarios it'd be reasonable to remove pending events (more
 * specifically, mark them DONE by the current subxact) but without a lot
 * of knowledge of the trigger semantics we can't do this in general.
 * ----------
 */
bool
AfterTriggerPendingOnRel(Oid relid)
{
	AfterTriggerEvent event;
	AfterTriggerEventChunk *chunk;
	int			depth;

	/* Scan queued events */
	for_each_event_chunk(event, chunk, afterTriggers.events)
	{
		AfterTriggerShared evtshared = GetTriggerSharedData(event);

		/*
		 * We can ignore completed events.  (Even if a DONE flag is rolled
		 * back by subxact abort, it's OK because the effects of the TRUNCATE
		 * or whatever must get rolled back too.)
		 */
		if (event->ate_flags & AFTER_TRIGGER_DONE)
			continue;

		if (evtshared->ats_relid == relid)
			return true;
	}

	/*
	 * Also scan events queued by incomplete queries.  This could only matter
	 * if TRUNCATE/etc is executed by a function or trigger within an updating
	 * query on the same relation, which is pretty perverse, but let's check.
	 */
	for (depth = 0; depth <= afterTriggers.query_depth && depth < afterTriggers.maxquerydepth; depth++)
	{
		for_each_event_chunk(event, chunk, afterTriggers.query_stack[depth].events)
		{
			AfterTriggerShared evtshared = GetTriggerSharedData(event);

			if (event->ate_flags & AFTER_TRIGGER_DONE)
				continue;

			if (evtshared->ats_relid == relid)
				return true;
		}
	}

	return false;
}

/* ----------
 * AfterTriggerSaveEvent()
 *
 *	Called by ExecA[RS]...Triggers() to queue up the triggers that should
 *	be fired for an event.
 *
 *	NOTE: this is called whenever there are any triggers associated with
 *	the event (even if they are disabled).  This function decides which
 *	triggers actually need to be queued.  It is also called after each row,
 *	even if there are no triggers for that event, if there are any AFTER
 *	STATEMENT triggers for the statement which use transition tables, so that
 *	the transition tuplestores can be built.  Furthermore, if the transition
 *	capture is happening for UPDATEd rows being moved to another partition due
 *	to the partition-key being changed, then this function is called once when
 *	the row is deleted (to capture OLD row), and once when the row is inserted
 *	into another partition (to capture NEW row).  This is done separately because
 *	DELETE and INSERT happen on different tables.
 *
 *	Transition tuplestores are built now, rather than when events are pulled
 *	off of the queue because AFTER ROW triggers are allowed to select from the
 *	transition tables for the statement.
 *
 *	This contains special support to queue the update events for the case where
 *	a partitioned table undergoing a cross-partition update may have foreign
 *	keys pointing into it.  Normally, a partitioned table's row triggers are
 *	not fired because the leaf partition(s) which are modified as a result of
 *	the operation on the partitioned table contain the same triggers which are
 *	fired instead. But that general scheme can cause problematic behavior with
 *	foreign key triggers during cross-partition updates, which are implemented
 *	as DELETE on the source partition followed by INSERT into the destination
 *	partition.  Specifically, firing DELETE triggers would lead to the wrong
 *	foreign key action to be enforced considering that the original command is
 *	UPDATE; in this case, this function is called with relinfo as the
 *	partitioned table, and src_partinfo and dst_partinfo referring to the
 *	source and target leaf partitions, respectively.
 *
 *	is_crosspart_update is true either when a DELETE event is fired on the
 *	source partition (which is to be ignored) or an UPDATE event is fired on
 *	the root partitioned table.
 * ----------
 */
static void
AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
					  ResultRelInfo *src_partinfo,
					  ResultRelInfo *dst_partinfo,
					  int event, bool row_trigger,
					  TupleTableSlot *oldslot, TupleTableSlot *newslot,
					  List *recheckIndexes, Bitmapset *modifiedCols,
					  TransitionCaptureState *transition_capture,
					  bool is_crosspart_update)
{
	Relation	rel = relinfo->ri_RelationDesc;
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
	AfterTriggerEventData new_event;
	AfterTriggerSharedData new_shared;
	char		relkind = rel->rd_rel->relkind;
	int			tgtype_event;
	int			tgtype_level;
	int			i;
	Tuplestorestate *fdw_tuplestore = NULL;

	/*
	 * Check state.  We use a normal test not Assert because it is possible to
	 * reach here in the wrong state given misconfigured RI triggers, in
	 * particular deferring a cascade action trigger.
	 */
	if (afterTriggers.query_depth < 0)
		elog(ERROR, "AfterTriggerSaveEvent() called outside of query");

	/* Be sure we have enough space to record events at this query depth. */
	if (afterTriggers.query_depth >= afterTriggers.maxquerydepth)
		AfterTriggerEnlargeQueryState();

	/*
	 * If the directly named relation has any triggers with transition tables,
	 * then we need to capture transition tuples.
	 */
	if (row_trigger && transition_capture != NULL)
	{
		TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;

		/*
		 * Capture the old tuple in the appropriate transition table based on
		 * the event.
		 */
		if (!TupIsNull(oldslot))
		{
			Tuplestorestate *old_tuplestore;

			old_tuplestore = GetAfterTriggersTransitionTable(event,
															 oldslot,
															 NULL,
															 transition_capture);
			TransitionTableAddTuple(estate, event, transition_capture, relinfo,
									oldslot, NULL, old_tuplestore);
		}

		/*
		 * Capture the new tuple in the appropriate transition table based on
		 * the event.
		 */
		if (!TupIsNull(newslot))
		{
			Tuplestorestate *new_tuplestore;

			new_tuplestore = GetAfterTriggersTransitionTable(event,
															 NULL,
															 newslot,
															 transition_capture);
			TransitionTableAddTuple(estate, event, transition_capture, relinfo,
									newslot, original_insert_tuple, new_tuplestore);
		}

		/*
		 * If transition tables are the only reason we're here, return. As
		 * mentioned above, we can also be here during update tuple routing in
		 * presence of transition tables, in which case this function is
		 * called separately for OLD and NEW, so we expect exactly one of them
		 * to be NULL.
		 */
		if (trigdesc == NULL ||
			(event == TRIGGER_EVENT_DELETE && !trigdesc->trig_delete_after_row) ||
			(event == TRIGGER_EVENT_INSERT && !trigdesc->trig_insert_after_row) ||
			(event == TRIGGER_EVENT_UPDATE && !trigdesc->trig_update_after_row) ||
			(event == TRIGGER_EVENT_UPDATE && (TupIsNull(oldslot) ^ TupIsNull(newslot))))
			return;
	}

	/*
	 * We normally don't see partitioned tables here for row level triggers
	 * except in the special case of a cross-partition update.  In that case,
	 * nodeModifyTable.c:ExecCrossPartitionUpdateForeignKey() calls here to
	 * queue an update event on the root target partitioned table, also
	 * passing the source and destination partitions and their tuples.
	 */
	Assert(!row_trigger ||
		   rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE ||
		   (is_crosspart_update &&
			TRIGGER_FIRED_BY_UPDATE(event) &&
			src_partinfo != NULL && dst_partinfo != NULL));

	/*
	 * Validate the event code and collect the associated tuple CTIDs.
	 *
	 * The event code will be used both as a bitmask and an array offset, so
	 * validation is important to make sure we don't walk off the edge of our
	 * arrays.
	 *
	 * Also, if we're considering statement-level triggers, check whether we
	 * already queued a set of them for this event, and cancel the prior set
	 * if so.  This preserves the behavior that statement-level triggers fire
	 * just once per statement and fire after row-level triggers.
	 */
	switch (event)
	{
		case TRIGGER_EVENT_INSERT:
			tgtype_event = TRIGGER_TYPE_INSERT;
			if (row_trigger)
			{
				Assert(oldslot == NULL);
				Assert(newslot != NULL);
				ItemPointerCopy(&(newslot->tts_tid), &(new_event.ate_ctid1));
				ItemPointerSetInvalid(&(new_event.ate_ctid2));
			}
			else
			{
				Assert(oldslot == NULL);
				Assert(newslot == NULL);
				ItemPointerSetInvalid(&(new_event.ate_ctid1));
				ItemPointerSetInvalid(&(new_event.ate_ctid2));
				cancel_prior_stmt_triggers(RelationGetRelid(rel),
										   CMD_INSERT, event);
			}
			break;
		case TRIGGER_EVENT_DELETE:
			tgtype_event = TRIGGER_TYPE_DELETE;
			if (row_trigger)
			{
				Assert(oldslot != NULL);
				Assert(newslot == NULL);
				ItemPointerCopy(&(oldslot->tts_tid), &(new_event.ate_ctid1));
				ItemPointerSetInvalid(&(new_event.ate_ctid2));
			}
			else
			{
				Assert(oldslot == NULL);
				Assert(newslot == NULL);
				ItemPointerSetInvalid(&(new_event.ate_ctid1));
				ItemPointerSetInvalid(&(new_event.ate_ctid2));
				cancel_prior_stmt_triggers(RelationGetRelid(rel),
										   CMD_DELETE, event);
			}
			break;
		case TRIGGER_EVENT_UPDATE:
			tgtype_event = TRIGGER_TYPE_UPDATE;
			if (row_trigger)
			{
				Assert(oldslot != NULL);
				Assert(newslot != NULL);
				ItemPointerCopy(&(oldslot->tts_tid), &(new_event.ate_ctid1));
				ItemPointerCopy(&(newslot->tts_tid), &(new_event.ate_ctid2));

				/*
				 * Also remember the OIDs of partitions to fetch these tuples
				 * out of later in AfterTriggerExecute().
				 */
				if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
				{
					Assert(src_partinfo != NULL && dst_partinfo != NULL);
					new_event.ate_src_part =
						RelationGetRelid(src_partinfo->ri_RelationDesc);
					new_event.ate_dst_part =
						RelationGetRelid(dst_partinfo->ri_RelationDesc);
				}
			}
			else
			{
				Assert(oldslot == NULL);
				Assert(newslot == NULL);
				ItemPointerSetInvalid(&(new_event.ate_ctid1));
				ItemPointerSetInvalid(&(new_event.ate_ctid2));
				cancel_prior_stmt_triggers(RelationGetRelid(rel),
										   CMD_UPDATE, event);
			}
			break;
		case TRIGGER_EVENT_TRUNCATE:
			tgtype_event = TRIGGER_TYPE_TRUNCATE;
			Assert(oldslot == NULL);
			Assert(newslot == NULL);
			ItemPointerSetInvalid(&(new_event.ate_ctid1));
			ItemPointerSetInvalid(&(new_event.ate_ctid2));
			break;
		default:
			elog(ERROR, "invalid after-trigger event code: %d", event);
			tgtype_event = 0;	/* keep compiler quiet */
			break;
	}

	/* Determine flags */
	if (!(relkind == RELKIND_FOREIGN_TABLE && row_trigger))
	{
		if (row_trigger && event == TRIGGER_EVENT_UPDATE)
		{
			if (relkind == RELKIND_PARTITIONED_TABLE)
				new_event.ate_flags = AFTER_TRIGGER_CP_UPDATE;
			else
				new_event.ate_flags = AFTER_TRIGGER_2CTID;
		}
		else
			new_event.ate_flags = AFTER_TRIGGER_1CTID;
	}

	/* else, we'll initialize ate_flags for each trigger */

	tgtype_level = (row_trigger ? TRIGGER_TYPE_ROW : TRIGGER_TYPE_STATEMENT);

	/*
	 * Must convert/copy the source and destination partition tuples into the
	 * root partitioned table's format/slot, because the processing in the
	 * loop below expects both oldslot and newslot tuples to be in that form.
	 */
	if (row_trigger && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		TupleTableSlot *rootslot;
		TupleConversionMap *map;

		rootslot = ExecGetTriggerOldSlot(estate, relinfo);
		map = ExecGetChildToRootMap(src_partinfo);
		if (map)
			oldslot = execute_attr_map_slot(map->attrMap,
											oldslot,
											rootslot);
		else
			oldslot = ExecCopySlot(rootslot, oldslot);

		rootslot = ExecGetTriggerNewSlot(estate, relinfo);
		map = ExecGetChildToRootMap(dst_partinfo);
		if (map)
			newslot = execute_attr_map_slot(map->attrMap,
											newslot,
											rootslot);
		else
			newslot = ExecCopySlot(rootslot, newslot);
	}

	for (i = 0; i < trigdesc->numtriggers; i++)
	{
		Trigger    *trigger = &trigdesc->triggers[i];

		if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
								  tgtype_level,
								  TRIGGER_TYPE_AFTER,
								  tgtype_event))
			continue;
		if (!TriggerEnabled(estate, relinfo, trigger, event,
							modifiedCols, oldslot, newslot))
			continue;

		if (relkind == RELKIND_FOREIGN_TABLE && row_trigger)
		{
			if (fdw_tuplestore == NULL)
			{
				fdw_tuplestore = GetCurrentFDWTuplestore();
				new_event.ate_flags = AFTER_TRIGGER_FDW_FETCH;
			}
			else
				/* subsequent event for the same tuple */
				new_event.ate_flags = AFTER_TRIGGER_FDW_REUSE;
		}

		/*
		 * If the trigger is a foreign key enforcement trigger, there are
		 * certain cases where we can skip queueing the event because we can
		 * tell by inspection that the FK constraint will still pass. There
		 * are also some cases during cross-partition updates of a partitioned
		 * table where queuing the event can be skipped.
		 */
		if (TRIGGER_FIRED_BY_UPDATE(event) || TRIGGER_FIRED_BY_DELETE(event))
		{
			switch (RI_FKey_trigger_type(trigger->tgfoid))
			{
				case RI_TRIGGER_PK:

					/*
					 * For cross-partitioned updates of partitioned PK table,
					 * skip the event fired by the component delete on the
					 * source leaf partition unless the constraint originates
					 * in the partition itself (!tgisclone), because the
					 * update event that will be fired on the root
					 * (partitioned) target table will be used to perform the
					 * necessary foreign key enforcement action.
					 */
					if (is_crosspart_update &&
						TRIGGER_FIRED_BY_DELETE(event) &&
						trigger->tgisclone)
						continue;

					/* Update or delete on trigger's PK table */
					if (!RI_FKey_pk_upd_check_required(trigger, rel,
													   oldslot, newslot))
					{
						/* skip queuing this event */
						continue;
					}
					break;

				case RI_TRIGGER_FK:

					/*
					 * Update on trigger's FK table.  We can skip the update
					 * event fired on a partitioned table during a
					 * cross-partition of that table, because the insert event
					 * that is fired on the destination leaf partition would
					 * suffice to perform the necessary foreign key check.
					 * Moreover, RI_FKey_fk_upd_check_required() expects to be
					 * passed a tuple that contains system attributes, most of
					 * which are not present in the virtual slot belonging to
					 * a partitioned table.
					 */
					if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
						!RI_FKey_fk_upd_check_required(trigger, rel,
													   oldslot, newslot))
					{
						/* skip queuing this event */
						continue;
					}
					break;

				case RI_TRIGGER_NONE:

					/*
					 * Not an FK trigger.  No need to queue the update event
					 * fired during a cross-partitioned update of a
					 * partitioned table, because the same row trigger must be
					 * present in the leaf partition(s) that are affected as
					 * part of this update and the events fired on them are
					 * queued instead.
					 */
					if (row_trigger &&
						rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
						continue;
					break;
			}
		}

		/*
		 * If the trigger is a deferred unique constraint check trigger, only
		 * queue it if the unique constraint was potentially violated, which
		 * we know from index insertion time.
		 */
		if (trigger->tgfoid == F_UNIQUE_KEY_RECHECK)
		{
			if (!list_member_oid(recheckIndexes, trigger->tgconstrindid))
				continue;		/* Uniqueness definitely not violated */
		}

		/*
		 * Fill in event structure and add it to the current query's queue.
		 * Note we set ats_table to NULL whenever this trigger doesn't use
		 * transition tables, to improve sharability of the shared event data.
		 */
		new_shared.ats_event =
			(event & TRIGGER_EVENT_OPMASK) |
			(row_trigger ? TRIGGER_EVENT_ROW : 0) |
			(trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) |
			(trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0);
		new_shared.ats_tgoid = trigger->tgoid;
		new_shared.ats_relid = RelationGetRelid(rel);
		new_shared.ats_rolid = GetUserId();
		new_shared.ats_firing_id = 0;
		if ((trigger->tgoldtable || trigger->tgnewtable) &&
			transition_capture != NULL)
		{
			switch (event)
			{
				case TRIGGER_EVENT_INSERT:
					new_shared.ats_table = transition_capture->tcs_insert_private;
					break;
				case TRIGGER_EVENT_UPDATE:
					new_shared.ats_table = transition_capture->tcs_update_private;
					break;
				case TRIGGER_EVENT_DELETE:
					new_shared.ats_table = transition_capture->tcs_delete_private;
					break;
				default:
					/* Must be TRUNCATE, see switch above */
					new_shared.ats_table = NULL;
					break;
			}
		}
		else
			new_shared.ats_table = NULL;
		new_shared.ats_modifiedcols = modifiedCols;

		afterTriggerAddEvent(&afterTriggers.query_stack[afterTriggers.query_depth].events,
							 &new_event, &new_shared);
	}

	/*
	 * Finally, spool any foreign tuple(s).  The tuplestore squashes them to
	 * minimal tuples, so this loses any system columns.  The executor lost
	 * those columns before us, for an unrelated reason, so this is fine.
	 */
	if (fdw_tuplestore)
	{
		if (oldslot != NULL)
			tuplestore_puttupleslot(fdw_tuplestore, oldslot);
		if (newslot != NULL)
			tuplestore_puttupleslot(fdw_tuplestore, newslot);
	}
}

/*
 * Detect whether we already queued BEFORE STATEMENT triggers for the given
 * relation + operation, and set the flag so the next call will report "true".
 */
static bool
before_stmt_triggers_fired(Oid relid, CmdType cmdType)
{
	bool		result;
	AfterTriggersTableData *table;

	/* Check state, like AfterTriggerSaveEvent. */
	if (afterTriggers.query_depth < 0)
		elog(ERROR, "before_stmt_triggers_fired() called outside of query");

	/* Be sure we have enough space to record events at this query depth. */
	if (afterTriggers.query_depth >= afterTriggers.maxquerydepth)
		AfterTriggerEnlargeQueryState();

	/*
	 * We keep this state in the AfterTriggersTableData that also holds
	 * transition tables for the relation + operation.  In this way, if we are
	 * forced to make a new set of transition tables because more tuples get
	 * entered after we've already fired triggers, we will allow a new set of
	 * statement triggers to get queued.
	 */
	table = GetAfterTriggersTableData(relid, cmdType);
	result = table->before_trig_done;
	table->before_trig_done = true;
	return result;
}

/*
 * If we previously queued a set of AFTER STATEMENT triggers for the given
 * relation + operation, and they've not been fired yet, cancel them.  The
 * caller will queue a fresh set that's after any row-level triggers that may
 * have been queued by the current sub-statement, preserving (as much as
 * possible) the property that AFTER ROW triggers fire before AFTER STATEMENT
 * triggers, and that the latter only fire once.  This deals with the
 * situation where several FK enforcement triggers sequentially queue triggers
 * for the same table into the same trigger query level.  We can't fully
 * prevent odd behavior though: if there are AFTER ROW triggers taking
 * transition tables, we don't want to change the transition tables once the
 * first such trigger has seen them.  In such a case, any additional events
 * will result in creating new transition tables and allowing new firings of
 * statement triggers.
 *
 * This also saves the current event list location so that a later invocation
 * of this function can cheaply find the triggers we're about to queue and
 * cancel them.
 */
static void
cancel_prior_stmt_triggers(Oid relid, CmdType cmdType, int tgevent)
{
	AfterTriggersTableData *table;
	AfterTriggersQueryData *qs = &afterTriggers.query_stack[afterTriggers.query_depth];

	/*
	 * We keep this state in the AfterTriggersTableData that also holds
	 * transition tables for the relation + operation.  In this way, if we are
	 * forced to make a new set of transition tables because more tuples get
	 * entered after we've already fired triggers, we will allow a new set of
	 * statement triggers to get queued without canceling the old ones.
	 */
	table = GetAfterTriggersTableData(relid, cmdType);

	if (table->after_trig_done)
	{
		/*
		 * We want to start scanning from the tail location that existed just
		 * before we inserted any statement triggers.  But the events list
		 * might've been entirely empty then, in which case scan from the
		 * current head.
		 */
		AfterTriggerEvent event;
		AfterTriggerEventChunk *chunk;

		if (table->after_trig_events.tail)
		{
			chunk = table->after_trig_events.tail;
			event = (AfterTriggerEvent) table->after_trig_events.tailfree;
		}
		else
		{
			chunk = qs->events.head;
			event = NULL;
		}

		for_each_chunk_from(chunk)
		{
			if (event == NULL)
				event = (AfterTriggerEvent) CHUNK_DATA_START(chunk);
			for_each_event_from(event, chunk)
			{
				AfterTriggerShared evtshared = GetTriggerSharedData(event);

				/*
				 * Exit loop when we reach events that aren't AS triggers for
				 * the target relation.
				 */
				if (evtshared->ats_relid != relid)
					goto done;
				if ((evtshared->ats_event & TRIGGER_EVENT_OPMASK) != tgevent)
					goto done;
				if (!TRIGGER_FIRED_FOR_STATEMENT(evtshared->ats_event))
					goto done;
				if (!TRIGGER_FIRED_AFTER(evtshared->ats_event))
					goto done;
				/* OK, mark it DONE */
				event->ate_flags &= ~AFTER_TRIGGER_IN_PROGRESS;
				event->ate_flags |= AFTER_TRIGGER_DONE;
			}
			/* signal we must reinitialize event ptr for next chunk */
			event = NULL;
		}
	}
done:

	/* In any case, save current insertion point for next time */
	table->after_trig_done = true;
	table->after_trig_events = qs->events;
}

/*
 * GUC assign_hook for session_replication_role
 */
void
assign_session_replication_role(int newval, void *extra)
{
	/*
	 * Must flush the plan cache when changing replication role; but don't
	 * flush unnecessarily.
	 */
	if (SessionReplicationRole != newval)
		ResetPlanCache();
}

/*
 * SQL function pg_trigger_depth()
 */
Datum
pg_trigger_depth(PG_FUNCTION_ARGS)
{
	PG_RETURN_INT32(MyTriggerDepth);
}

/*
 * Check whether a trigger modified a virtual generated column and replace the
 * value with null if so.
 *
 * We need to check this so that we don't end up storing a non-null value in a
 * virtual generated column.
 *
 * We don't need to check for stored generated columns, since those will be
 * overwritten later anyway.
 */
static HeapTuple
check_modified_virtual_generated(TupleDesc tupdesc, HeapTuple tuple)
{
	if (!(tupdesc->constr && tupdesc->constr->has_generated_virtual))
		return tuple;

	for (int i = 0; i < tupdesc->natts; i++)
	{
		if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
		{
			if (!heap_attisnull(tuple, i + 1, tupdesc))
			{
				int			replCol = i + 1;
				Datum		replValue = 0;
				bool		replIsnull = true;

				tuple = heap_modify_tuple_by_cols(tuple, tupdesc, 1, &replCol, &replValue, &replIsnull);
			}
		}
	}

	return tuple;
}
