diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index d9a24a344848da8c1d7caeb1fe92627798794da6..8bbd1942eba02fa7a0c7c51be0a02501c28a651c 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$PostgreSQL: pgsql/src/backend/executor/execAmi.c,v 1.77 2003/12/18 20:21:37 tgl Exp $
+ *	$PostgreSQL: pgsql/src/backend/executor/execAmi.c,v 1.78 2004/03/02 18:56:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -348,3 +348,68 @@ ExecSupportsBackwardScan(Plan *node)
 			return false;
 	}
 }
+
+/*
+ * ExecMayReturnRawTuples
+ *		Check whether a plan tree may return "raw" disk tuples (that is,
+ *		pointers to original data in disk buffers, as opposed to temporary
+ *		tuples constructed by projection steps).  In the case of Append,
+ *		some subplans may return raw tuples and others projected tuples;
+ *		we return "true" if any of the returned tuples could be raw.
+ *
+ * This must be passed an already-initialized planstate tree, because we
+ * need to look at the results of ExecAssignScanProjectionInfo().
+ */
+bool
+ExecMayReturnRawTuples(PlanState *node)
+{
+	/*
+	 * At a table scan node, we check whether ExecAssignScanProjectionInfo
+	 * decided to do projection or not.  Most non-scan nodes always project
+	 * and so we can return "false" immediately.  For nodes that don't
+	 * project but just pass up input tuples, we have to recursively
+	 * examine the input plan node.
+	 *
+	 * Note: Hash and Material are listed here because they sometimes
+	 * return an original input tuple, not a copy.  But Sort and SetOp
+	 * never return an original tuple, so they can be treated like
+	 * projecting nodes.
+	 */
+	switch (nodeTag(node))
+	{
+		/* Table scan nodes */
+		case T_SeqScanState:
+		case T_IndexScanState:
+		case T_TidScanState:
+		case T_SubqueryScanState:
+		case T_FunctionScanState:
+			if (node->ps_ProjInfo == NULL)
+				return true;
+			break;
+
+		/* Non-projecting nodes */
+		case T_HashState:
+		case T_MaterialState:
+		case T_UniqueState:
+		case T_LimitState:
+			return ExecMayReturnRawTuples(node->lefttree);
+
+		case T_AppendState:
+		{
+			AppendState *appendstate = (AppendState *) node;
+			int			j;
+
+			for (j = 0; j < appendstate->as_nplans; j++)
+			{
+				if (ExecMayReturnRawTuples(appendstate->appendplans[j]))
+					return true;
+			}
+			break;
+		}
+
+		/* All projecting node types come here */
+		default:
+			break;
+	}
+	return false;
+}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4f36602aa048ffb4f656df80b902f5620e86b195..caae6e880e5ed3edb88072336cedd1260bbd7176 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.228 2004/01/22 02:23:21 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.229 2004/03/02 18:56:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -659,10 +659,10 @@ InitPlan(QueryDesc *queryDesc, bool explainOnly)
 	/*
 	 * Initialize the junk filter if needed.  SELECT and INSERT queries
 	 * need a filter if there are any junk attrs in the tlist.	INSERT and
-	 * SELECT INTO also need a filter if the top plan node is a scan node
-	 * that's not doing projection (else we'll be scribbling on the scan
-	 * tuple!)	UPDATE and DELETE always need a filter, since there's
-	 * always a junk 'ctid' attribute present --- no need to look first.
+	 * SELECT INTO also need a filter if the plan may return raw disk tuples
+	 * (else heap_insert will be scribbling on the source relation!).
+	 * UPDATE and DELETE always need a filter, since there's always a junk
+	 * 'ctid' attribute present --- no need to look first.
 	 */
 	{
 		bool		junk_filter_needed = false;
@@ -683,18 +683,9 @@ InitPlan(QueryDesc *queryDesc, bool explainOnly)
 					}
 				}
 				if (!junk_filter_needed &&
-					(operation == CMD_INSERT || do_select_into))
-				{
-					if (IsA(planstate, SeqScanState) ||
-						IsA(planstate, IndexScanState) ||
-						IsA(planstate, TidScanState) ||
-						IsA(planstate, SubqueryScanState) ||
-						IsA(planstate, FunctionScanState))
-					{
-						if (planstate->ps_ProjInfo == NULL)
-							junk_filter_needed = true;
-					}
-				}
+					(operation == CMD_INSERT || do_select_into) &&
+					ExecMayReturnRawTuples(planstate))
+					junk_filter_needed = true;
 				break;
 			case CMD_UPDATE:
 			case CMD_DELETE:
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 39e07da8afb56cd716387e29a95ad80144de47e9..cc2e49efafc2653216d25102d408a64a0478e6a1 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.106 2004/01/22 02:23:21 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.107 2004/03/02 18:56:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -36,6 +36,7 @@ extern void ExecMarkPos(PlanState *node);
 extern void ExecRestrPos(PlanState *node);
 extern bool ExecSupportsMarkRestore(NodeTag plantype);
 extern bool ExecSupportsBackwardScan(Plan *node);
+extern bool ExecMayReturnRawTuples(PlanState *node);
 
 /*
  * prototypes from functions in execGrouping.c