From 76ce39e386fdcd73c864ac1cf79cb81547e54e4f Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 14 Nov 2005 17:42:55 +0000
Subject: [PATCH] Prevent ExecInsert() and ExecUpdate() from scribbling on the
 result tuple slot of the topmost plan node when a trigger returns a modified
 tuple. These appear to be the only places where a plan node's caller did not
 treat the result slot as read-only, which is an assumption that nodeUnique
 makes as of 8.1.  Fixes trigger-vs-DISTINCT bug reported by Frank van Vugt.

---
 src/backend/executor/execMain.c  | 38 +++++++++++++++++++++++++-------
 src/backend/executor/execUtils.c |  4 +++-
 src/include/nodes/execnodes.h    |  4 +++-
 3 files changed, 36 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 2a96a161c81..c379802cafd 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.256 2005/10/15 02:49:16 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.257 2005/11/14 17:42:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -582,7 +582,8 @@ InitPlan(QueryDesc *queryDesc, bool explainOnly)
 	 * initialize the executor "tuple" table.  We need slots for all the plan
 	 * nodes, plus possibly output slots for the junkfilter(s). At this point
 	 * we aren't sure if we need junkfilters, so just add slots for them
-	 * unconditionally.
+	 * unconditionally.  Also, if it's not a SELECT, set up a slot for use
+	 * for trigger output tuples.
 	 */
 	{
 		int			nSlots = ExecCountSlotsNode(plan);
@@ -591,7 +592,14 @@ InitPlan(QueryDesc *queryDesc, bool explainOnly)
 			nSlots += list_length(parseTree->resultRelations);
 		else
 			nSlots += 1;
+		if (operation != CMD_SELECT)
+			nSlots++;
+
 		estate->es_tupleTable = ExecCreateTupleTable(nSlots);
+
+		if (operation != CMD_SELECT)
+			estate->es_trig_tuple_slot =
+				ExecAllocTableSlot(estate->es_tupleTable);
 	}
 
 	/* mark EvalPlanQual not active */
@@ -1399,12 +1407,19 @@ ExecInsert(TupleTableSlot *slot,
 		if (newtuple != tuple)	/* modified by Trigger(s) */
 		{
 			/*
-			 * Insert modified tuple into tuple table slot, replacing the
-			 * original.  We assume that it was allocated in per-tuple memory
+			 * Put the modified tuple into a slot for convenience of routines
+			 * below.  We assume the tuple was allocated in per-tuple memory
 			 * context, and therefore will go away by itself. The tuple table
 			 * slot should not try to clear it.
 			 */
-			ExecStoreTuple(newtuple, slot, InvalidBuffer, false);
+			TupleTableSlot *newslot = estate->es_trig_tuple_slot;
+
+			if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor)
+				ExecSetSlotDescriptor(newslot,
+									  slot->tts_tupleDescriptor,
+									  false);
+			ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
+			slot = newslot;
 			tuple = newtuple;
 		}
 	}
@@ -1600,12 +1615,19 @@ ExecUpdate(TupleTableSlot *slot,
 		if (newtuple != tuple)	/* modified by Trigger(s) */
 		{
 			/*
-			 * Insert modified tuple into tuple table slot, replacing the
-			 * original.  We assume that it was allocated in per-tuple memory
+			 * Put the modified tuple into a slot for convenience of routines
+			 * below.  We assume the tuple was allocated in per-tuple memory
 			 * context, and therefore will go away by itself. The tuple table
 			 * slot should not try to clear it.
 			 */
-			ExecStoreTuple(newtuple, slot, InvalidBuffer, false);
+			TupleTableSlot *newslot = estate->es_trig_tuple_slot;
+
+			if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor)
+				ExecSetSlotDescriptor(newslot,
+									  slot->tts_tupleDescriptor,
+									  false);
+			ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
+			slot = newslot;
 			tuple = newtuple;
 		}
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 05bfc08dc7d..d9bcd1cf6fe 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.126 2005/10/15 02:49:16 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.127 2005/11/14 17:42:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -187,6 +187,8 @@ CreateExecutorState(void)
 
 	estate->es_junkFilter = NULL;
 
+	estate->es_trig_tuple_slot = NULL;
+
 	estate->es_into_relation_descriptor = NULL;
 	estate->es_into_relation_use_wal = false;
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8b06e2897d9..c638c28a02b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.139 2005/10/15 02:49:45 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.140 2005/11/14 17:42:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -304,6 +304,8 @@ typedef struct EState
 	ResultRelInfo *es_result_relation_info;		/* currently active array elt */
 	JunkFilter *es_junkFilter;	/* currently active junk filter */
 
+	TupleTableSlot *es_trig_tuple_slot;			/* for trigger output tuples */
+
 	Relation	es_into_relation_descriptor;	/* for SELECT INTO */
 	bool		es_into_relation_use_wal;
 
-- 
GitLab