triggers-in-modify-table-1.patch

text/x-patch

Filename: triggers-in-modify-table-1.patch
Type: text/x-patch
Part: 0
Message: WIP: push AFTER-trigger execution into ModifyTable node

Patch

Format: context
File+
src/backend/commands/explain.c 3 16
src/backend/commands/portalcmds.c 0 2
src/backend/commands/trigger.c 1 1
src/backend/executor/execMain.c 1 0
src/backend/executor/execUtils.c 1 0
src/backend/executor/functions.c 0 20
src/backend/executor/nodeModifyTable.c 43 25
src/backend/executor/spi.c 3 8
src/backend/tcop/pquery.c 0 15
src/include/nodes/execnodes.h 2 1
Index: src/backend/commands/explain.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/commands/explain.c,v
retrieving revision 1.192
diff -c -r1.192 explain.c
*** src/backend/commands/explain.c	12 Oct 2009 18:10:41 -0000	1.192
--- src/backend/commands/explain.c	28 Oct 2009 22:24:42 -0000
***************
*** 19,25 ****
  #include "commands/defrem.h"
  #include "commands/explain.h"
  #include "commands/prepare.h"
- #include "commands/trigger.h"
  #include "executor/instrument.h"
  #include "optimizer/clauses.h"
  #include "optimizer/planner.h"
--- 19,24 ----
***************
*** 354,363 ****
  
  	INSTR_TIME_SET_CURRENT(starttime);
  
- 	/* If analyzing, we need to cope with queued triggers */
- 	if (es->analyze)
- 		AfterTriggerBeginQuery();
- 
  	/* Select execution options */
  	if (es->analyze)
  		eflags = 0;				/* default run-to-completion flags */
--- 353,358 ----
***************
*** 383,402 ****
  	ExplainPrintPlan(es, queryDesc);
  
  	/*
! 	 * If we ran the command, run any AFTER triggers it queued.  (Note this
! 	 * will not include DEFERRED triggers; since those don't run until end of
! 	 * transaction, we can't measure them.)  Include into total runtime.
  	 */
  	if (es->analyze)
  	{
- 		INSTR_TIME_SET_CURRENT(starttime);
- 		AfterTriggerEndQuery(queryDesc->estate);
- 		totaltime += elapsed_time(&starttime);
- 	}
- 
- 	/* Print info about runtime of triggers */
- 	if (es->analyze)
- 	{
  		ResultRelInfo *rInfo;
  		bool		show_relname;
  		int			numrels = queryDesc->estate->es_num_result_relations;
--- 378,389 ----
  	ExplainPrintPlan(es, queryDesc);
  
  	/*
! 	 * Print info about runtime of triggers.  (Note this will not include
! 	 * DEFERRED triggers; since those don't run until end of transaction, we
! 	 * can't measure them.)
  	 */
  	if (es->analyze)
  	{
  		ResultRelInfo *rInfo;
  		bool		show_relname;
  		int			numrels = queryDesc->estate->es_num_result_relations;
Index: src/backend/commands/portalcmds.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v
retrieving revision 1.81
diff -c -r1.81 portalcmds.c
*** src/backend/commands/portalcmds.c	7 Oct 2009 16:27:18 -0000	1.81
--- src/backend/commands/portalcmds.c	28 Oct 2009 22:24:42 -0000
***************
*** 264,270 ****
  			PG_TRY();
  			{
  				CurrentResourceOwner = portal->resowner;
- 				/* we do not need AfterTriggerEndQuery() here */
  				ExecutorEnd(queryDesc);
  				FreeQueryDesc(queryDesc);
  			}
--- 264,269 ----
***************
*** 371,377 ****
  		 * Now shut down the inner executor.
  		 */
  		portal->queryDesc = NULL;		/* prevent double shutdown */
- 		/* we do not need AfterTriggerEndQuery() here */
  		ExecutorEnd(queryDesc);
  		FreeQueryDesc(queryDesc);
  
--- 370,375 ----
Index: src/backend/commands/trigger.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/commands/trigger.c,v
retrieving revision 1.256
diff -c -r1.256 trigger.c
*** src/backend/commands/trigger.c	27 Oct 2009 20:14:27 -0000	1.256
--- src/backend/commands/trigger.c	28 Oct 2009 22:24:42 -0000
***************
*** 3145,3151 ****
   *	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 should be called just BEFORE closing down the executor
   *	with ExecutorEnd, because we make use of the EState's info about
   *	target relations.
   * ----------
--- 3145,3151 ----
   *	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.
   * ----------
Index: src/backend/executor/execMain.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/executor/execMain.c,v
retrieving revision 1.334
diff -c -r1.334 execMain.c
*** src/backend/executor/execMain.c	26 Oct 2009 02:26:29 -0000	1.334
--- src/backend/executor/execMain.c	28 Oct 2009 22:24:42 -0000
***************
*** 1886,1891 ****
--- 1886,1892 ----
  	estate->es_result_relation_info = parentestate->es_result_relation_info;
  	/* es_trig_target_relations must NOT be copied */
  	estate->es_rowMarks = parentestate->es_rowMarks;
+ 	estate->es_after_triggers = false;	/* shouldn't be any anyway */
  	estate->es_instrument = parentestate->es_instrument;
  	estate->es_select_into = parentestate->es_select_into;
  	estate->es_into_oids = parentestate->es_into_oids;
Index: src/backend/executor/execUtils.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/executor/execUtils.c,v
retrieving revision 1.165
diff -c -r1.165 execUtils.c
*** src/backend/executor/execUtils.c	26 Oct 2009 02:26:29 -0000	1.165
--- src/backend/executor/execUtils.c	28 Oct 2009 22:24:43 -0000
***************
*** 130,135 ****
--- 130,136 ----
  	estate->es_processed = 0;
  	estate->es_lastoid = InvalidOid;
  
+ 	estate->es_after_triggers = true;	/* fire AFTER triggers by default */
  	estate->es_instrument = false;
  	estate->es_select_into = false;
  	estate->es_into_oids = false;
Index: src/backend/executor/functions.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/executor/functions.c,v
retrieving revision 1.135
diff -c -r1.135 functions.c
*** src/backend/executor/functions.c	11 Jun 2009 17:25:38 -0000	1.135
--- src/backend/executor/functions.c	28 Oct 2009 22:24:43 -0000
***************
*** 17,23 ****
  #include "access/xact.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_type.h"
- #include "commands/trigger.h"
  #include "executor/functions.h"
  #include "funcapi.h"
  #include "miscadmin.h"
--- 17,22 ----
***************
*** 426,442 ****
  
  	/* Utility commands don't need Executor. */
  	if (es->qd->utilitystmt == NULL)
- 	{
- 		/*
- 		 * Only set up to collect queued triggers if it's not a SELECT. This
- 		 * isn't just an optimization, but is necessary in case a SELECT
- 		 * returns multiple rows to caller --- we mustn't exit from the
- 		 * function execution with a stacked AfterTrigger level still active.
- 		 */
- 		if (es->qd->operation != CMD_SELECT)
- 			AfterTriggerBeginQuery();
  		ExecutorStart(es->qd, 0);
- 	}
  
  	es->status = F_EXEC_RUN;
  }
--- 425,431 ----
***************
*** 492,508 ****
  
  	/* Utility commands don't need Executor. */
  	if (es->qd->utilitystmt == NULL)
- 	{
- 		/* Make our snapshot the active one for any called functions */
- 		PushActiveSnapshot(es->qd->snapshot);
- 
- 		if (es->qd->operation != CMD_SELECT)
- 			AfterTriggerEndQuery(es->qd->estate);
  		ExecutorEnd(es->qd);
  
- 		PopActiveSnapshot();
- 	}
- 
  	(*es->qd->dest->rDestroy) (es->qd->dest);
  
  	FreeQueryDesc(es->qd);
--- 481,488 ----
Index: src/backend/executor/nodeModifyTable.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/executor/nodeModifyTable.c,v
retrieving revision 1.2
diff -c -r1.2 nodeModifyTable.c
*** src/backend/executor/nodeModifyTable.c	26 Oct 2009 02:26:31 -0000	1.2
--- src/backend/executor/nodeModifyTable.c	28 Oct 2009 22:24:43 -0000
***************
*** 578,632 ****
  
  
  /*
!  * Process BEFORE EACH STATEMENT triggers
   */
  static void
! fireBSTriggers(ModifyTableState *node)
  {
  	switch (node->operation)
  	{
  		case CMD_INSERT:
! 			ExecBSInsertTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		case CMD_UPDATE:
! 			ExecBSUpdateTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		case CMD_DELETE:
! 			ExecBSDeleteTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		default:
  			elog(ERROR, "unknown operation");
  			break;
  	}
  }
  
  /*
!  * Process AFTER EACH STATEMENT triggers
   */
  static void
! fireASTriggers(ModifyTableState *node)
  {
  	switch (node->operation)
  	{
  		case CMD_INSERT:
! 			ExecASInsertTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		case CMD_UPDATE:
! 			ExecASUpdateTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		case CMD_DELETE:
! 			ExecASDeleteTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		default:
  			elog(ERROR, "unknown operation");
  			break;
  	}
  }
  
  
--- 578,653 ----
  
  
  /*
!  * Initialize trigger processing during first execution of node
   */
  static void
! begin_statement_triggers(ModifyTableState *node, EState *estate)
  {
+ 	/*
+ 	 * Normally, we start a "query" to collect AFTER trigger events.
+ 	 * However, if caller of Executor has cleared es_after_triggers, any
+ 	 * AFTER triggers will be queued in the outer query instead (and
+ 	 * there had better be one!).
+ 	 */
+ 	if (estate->es_after_triggers)
+ 		AfterTriggerBeginQuery();
+ 
+ 	/*
+ 	 * Process BEFORE EACH STATEMENT triggers
+ 	 */
  	switch (node->operation)
  	{
  		case CMD_INSERT:
! 			ExecBSInsertTriggers(estate, estate->es_result_relations);
  			break;
  		case CMD_UPDATE:
! 			ExecBSUpdateTriggers(estate, estate->es_result_relations);
  			break;
  		case CMD_DELETE:
! 			ExecBSDeleteTriggers(estate, estate->es_result_relations);
  			break;
  		default:
  			elog(ERROR, "unknown operation");
  			break;
  	}
+ 
+ 	node->triggers_ready = true;
  }
  
  /*
!  * Shut down trigger processing after final execution of node
   */
  static void
! end_statement_triggers(ModifyTableState *node, EState *estate)
  {
+ 	/*
+ 	 * Process AFTER EACH STATEMENT triggers
+ 	 */
  	switch (node->operation)
  	{
  		case CMD_INSERT:
! 			ExecASInsertTriggers(estate, estate->es_result_relations);
  			break;
  		case CMD_UPDATE:
! 			ExecASUpdateTriggers(estate, estate->es_result_relations);
  			break;
  		case CMD_DELETE:
! 			ExecASDeleteTriggers(estate, estate->es_result_relations);
  			break;
  		default:
  			elog(ERROR, "unknown operation");
  			break;
  	}
+ 
+ 	/*
+ 	 * Execute any queued AFTER triggers, or move them to the deferred
+ 	 * list if they're deferred.  If not es_after_triggers, they stay
+ 	 * in the outer query's queue.
+ 	 */
+ 	if (estate->es_after_triggers)
+ 		AfterTriggerEndQuery(estate);
+ 
+ 	node->triggers_ready = false;
  }
  
  
***************
*** 650,662 ****
  	ItemPointerData tuple_ctid;
  
  	/*
! 	 * On first call, fire BEFORE STATEMENT triggers before proceeding.
  	 */
! 	if (node->fireBSTriggers)
! 	{
! 		fireBSTriggers(node);
! 		node->fireBSTriggers = false;
! 	}
  
  	/*
  	 * es_result_relation_info must point to the currently active result
--- 671,680 ----
  	ItemPointerData tuple_ctid;
  
  	/*
! 	 * On first call, initialize trigger processing before proceeding.
  	 */
! 	if (!node->triggers_ready)
! 		begin_statement_triggers(node, estate);
  
  	/*
  	 * es_result_relation_info must point to the currently active result
***************
*** 758,766 ****
  	estate->es_result_relation_info = NULL;
  
  	/*
! 	 * We're done, but fire AFTER STATEMENT triggers before exiting.
  	 */
! 	fireASTriggers(node);
  
  	return NULL;
  }
--- 776,784 ----
  	estate->es_result_relation_info = NULL;
  
  	/*
! 	 * We're done, but finish trigger processing before exiting.
  	 */
! 	end_statement_triggers(node, estate);
  
  	return NULL;
  }
***************
*** 805,811 ****
  	mtstate->operation = operation;
  	/* set up epqstate with dummy subplan pointer for the moment */
  	EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, node->epqParam);
! 	mtstate->fireBSTriggers = true;
  
  	/* For the moment, assume our targets are exactly the global result rels */
  
--- 823,829 ----
  	mtstate->operation = operation;
  	/* set up epqstate with dummy subplan pointer for the moment */
  	EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, node->epqParam);
! 	mtstate->triggers_ready = false;
  
  	/* For the moment, assume our targets are exactly the global result rels */
  
Index: src/backend/executor/spi.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/executor/spi.c,v
retrieving revision 1.210
diff -c -r1.210 spi.c
*** src/backend/executor/spi.c	10 Oct 2009 01:43:47 -0000	1.210
--- src/backend/executor/spi.c	28 Oct 2009 22:24:43 -0000
***************
*** 19,25 ****
  #include "access/xact.h"
  #include "catalog/heap.h"
  #include "catalog/pg_type.h"
- #include "commands/trigger.h"
  #include "executor/executor.h"
  #include "executor/spi_priv.h"
  #include "tcop/pquery.h"
--- 19,24 ----
***************
*** 2001,2011 ****
  		ResetUsage();
  #endif
  
- 	if (fire_triggers)
- 		AfterTriggerBeginQuery();
- 
  	ExecutorStart(queryDesc, 0);
  
  	ExecutorRun(queryDesc, ForwardScanDirection, tcount);
  
  	_SPI_current->processed = queryDesc->estate->es_processed;
--- 2000,2010 ----
  		ResetUsage();
  #endif
  
  	ExecutorStart(queryDesc, 0);
  
+ 	/* tell any ModifyTable nodes whether to handle AFTER triggers */
+ 	queryDesc->estate->es_after_triggers = fire_triggers;
+ 
  	ExecutorRun(queryDesc, ForwardScanDirection, tcount);
  
  	_SPI_current->processed = queryDesc->estate->es_processed;
***************
*** 2018,2027 ****
  			elog(ERROR, "consistency check on SPI tuple count failed");
  	}
  
- 	/* Take care of any queued AFTER triggers */
- 	if (fire_triggers)
- 		AfterTriggerEndQuery(queryDesc->estate);
- 
  	ExecutorEnd(queryDesc);
  	/* FreeQueryDesc is done by the caller */
  
--- 2017,2022 ----
Index: src/backend/tcop/pquery.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/tcop/pquery.c,v
retrieving revision 1.132
diff -c -r1.132 pquery.c
*** src/backend/tcop/pquery.c	10 Oct 2009 01:43:49 -0000	1.132
--- src/backend/tcop/pquery.c	28 Oct 2009 22:24:43 -0000
***************
*** 181,191 ****
  								dest, params, false);
  
  	/*
- 	 * Set up to collect AFTER triggers
- 	 */
- 	AfterTriggerBeginQuery();
- 
- 	/*
  	 * Call ExecutorStart to prepare the plan for execution
  	 */
  	ExecutorStart(queryDesc, 0);
--- 181,186 ----
***************
*** 229,237 ****
  		}
  	}
  
- 	/* Now take care of any queued AFTER triggers */
- 	AfterTriggerEndQuery(queryDesc->estate);
- 
  	PopActiveSnapshot();
  
  	/*
--- 224,229 ----
***************
*** 518,530 ****
  											false);
  
  				/*
- 				 * We do *not* call AfterTriggerBeginQuery() here.	We assume
- 				 * that a SELECT cannot queue any triggers.  It would be messy
- 				 * to support triggers since the execution of the portal may
- 				 * be interleaved with other queries.
- 				 */
- 
- 				/*
  				 * If it's a scrollable cursor, executor needs to support
  				 * REWIND and backwards scan.
  				 */
--- 510,515 ----
Index: src/include/nodes/execnodes.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/nodes/execnodes.h,v
retrieving revision 1.211
diff -c -r1.211 execnodes.h
*** src/include/nodes/execnodes.h	26 Oct 2009 02:26:41 -0000	1.211
--- src/include/nodes/execnodes.h	28 Oct 2009 22:24:43 -0000
***************
*** 361,366 ****
--- 361,367 ----
  	uint32		es_processed;	/* # of tuples processed */
  	Oid			es_lastoid;		/* last oid processed (by INSERT) */
  
+ 	bool		es_after_triggers;	/* true to execute AFTER triggers */
  	bool		es_instrument;	/* true requests runtime instrumentation */
  	bool		es_select_into; /* true if doing SELECT INTO */
  	bool		es_into_oids;	/* true to generate OIDs in SELECT INTO */
***************
*** 1022,1028 ****
  	int				mt_nplans;		/* number of plans in the array */
  	int				mt_whichplan;	/* which one is being executed (0..n-1) */
  	EPQState		mt_epqstate;	/* for evaluating EvalPlanQual rechecks */
! 	bool			fireBSTriggers;	/* do we need to fire stmt triggers? */
  } ModifyTableState;
  
  /* ----------------
--- 1023,1029 ----
  	int				mt_nplans;		/* number of plans in the array */
  	int				mt_whichplan;	/* which one is being executed (0..n-1) */
  	EPQState		mt_epqstate;	/* for evaluating EvalPlanQual rechecks */
! 	bool			triggers_ready;	/* are statement-level triggers done? */
  } ModifyTableState;
  
  /* ----------------