writeablecte.patch

text/plain

Filename: writeablecte.patch
Type: text/plain
Part: 0
Message: Writeable CTE patch

Patch

Format: unified
File+
doc/src/sgml/queries.sgml 30 5
doc/src/sgml/ref/select.sgml 1 1
src/backend/commands/copy.c 2 1
src/backend/commands/portalcmds.c 5 0
src/backend/commands/vacuum.c 2 2
src/backend/commands/view.c 13 0
src/backend/executor/execMain.c 89 12
src/backend/executor/execUtils.c 2 3
src/backend/executor/nodeModifyTable.c 48 45
src/backend/nodes/copyfuncs.c 2 0
src/backend/nodes/nodeFuncs.c 44 0
src/backend/nodes/outfuncs.c 2 1
src/backend/optimizer/plan/createplan.c 0 4
src/backend/optimizer/plan/planner.c 7 13
src/backend/optimizer/plan/setrefs.c 4 0
src/backend/optimizer/plan/subselect.c 22 2
src/backend/parser/gram.y 27 0
src/backend/parser/parse_cte.c 14 10
src/backend/parser/parse_relation.c 1 0
src/backend/parser/parse_target.c 23 4
src/backend/rewrite/rewriteHandler.c 53 1
src/backend/tcop/pquery.c 1 0
src/backend/utils/adt/ruleutils.c 10 3
src/include/executor/executor.h 2 1
src/include/nodes/execnodes.h 2 0
src/include/nodes/plannodes.h 3 0
src/include/nodes/relation.h 4 2
src/test/regress/expected/with.out 82 0
src/test/regress/sql/with.sql 35 0
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index b2741bc..3aa7da5 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1499,7 +1499,7 @@ SELECT 3, 'three';
 <synopsis>
 SELECT <replaceable>select_list</replaceable> FROM <replaceable>table_expression</replaceable>
 </synopsis>
-   and can appear anywhere a <literal>SELECT</> can.  For example, you can
+   and can appear anywhere a <literal>SELECT</literal> can.  For example, you can
    use it as part of a <literal>UNION</>, or attach a
    <replaceable>sort_specification</replaceable> (<literal>ORDER BY</>,
    <literal>LIMIT</>, and/or <literal>OFFSET</>) to it.  <literal>VALUES</>
@@ -1529,10 +1529,11 @@ SELECT <replaceable>select_list</replaceable> FROM <replaceable>table_expression
   </indexterm>
 
   <para>
-   <literal>WITH</> provides a way to write subqueries for use in a larger
-   <literal>SELECT</> query.  The subqueries can be thought of as defining
-   temporary tables that exist just for this query.  One use of this feature
-   is to break down complicated queries into simpler parts.  An example is:
+   <literal>WITH</> provides a way to write subqueries for use in a
+   larger query.  The subqueries can be thought of as defining
+   temporary tables that exist just for this query.  One use of this
+   feature is to break down complicated queries into simpler parts.
+   An example is:
 
 <programlisting>
 WITH regional_sales AS (
@@ -1560,6 +1561,30 @@ GROUP BY region, product;
   </para>
 
   <para>
+  A <literal>WITH</literal> clause can also have an
+  <literal>INSERT</literal>, <literal>UPDATE</literal> or
+  <literal>DELETE</literal> (each optionally with a
+  <literal>RETURNING</literal> clause) statement in it.  The example below
+  moves rows from the main table, foo_log into a partition,
+  foo_log_200910.
+
+<programlisting>
+WITH rows AS (
+        DELETE FROM ONLY foo_log
+        WHERE
+           foo_date &gt;= '2009-10-01' AND
+           foo_date &lt;  '2009-11-01'
+           RETURNING *
+     ), t AS (
+           INSERT INTO foo_log_200910
+           SELECT * FROM rows
+     )
+VALUES(true);
+</programlisting>
+
+  </para>
+
+  <para>
    The optional <literal>RECURSIVE</> modifier changes <literal>WITH</>
    from a mere syntactic convenience into a feature that accomplishes
    things not otherwise possible in standard SQL.  Using
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 8954693..3634d43 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -58,7 +58,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
 
 <phrase>and <replaceable class="parameter">with_query</replaceable> is:</phrase>
 
-    <replaceable class="parameter">with_query_name</replaceable> [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ] AS ( <replaceable class="parameter">select</replaceable> )
+    <replaceable class="parameter">with_query_name</replaceable> [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ] AS ( <replaceable class="parameter">select</replaceable> | (<replaceable class="parameter">insert</replaceable> | <replaceable class="parameter">update</replaceable> | <replaceable class="parameter">delete</replaceable> [ RETURNING...]))
 
 TABLE { [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] | <replaceable class="parameter">with_query_name</replaceable> }
 </synopsis>
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 9100dd9..78d2344 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2160,7 +2160,8 @@ CopyFrom(CopyState cstate)
 			heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
 
 			if (resultRelInfo->ri_NumIndices > 0)
-				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+				recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
+													   slot, &(tuple->t_self),
 													   estate, false);
 
 			/* AFTER ROW INSERT Triggers */
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 9ca2e84..9e1f05d 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -48,6 +48,11 @@ PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
 	Portal		portal;
 	MemoryContext oldContext;
 
+	if (stmt->hasWritableCtes)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Non-SELECT cursors are not implemented")));
+
 	if (cstmt == NULL || !IsA(cstmt, DeclareCursorStmt))
 		elog(ERROR, "PerformCursorOpen called for non-cursor query");
 
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7f2e270..a91b49a 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -3088,7 +3088,7 @@ move_chain_tuple(Relation rel,
 	if (ec->resultRelInfo->ri_NumIndices > 0)
 	{
 		ExecStoreTuple(&newtup, ec->slot, InvalidBuffer, false);
-		ExecInsertIndexTuples(ec->slot, &(newtup.t_self), ec->estate, true);
+		ExecInsertIndexTuples(ec->resultRelInfo, ec->slot, &(newtup.t_self), ec->estate, true);
 		ResetPerTupleExprContext(ec->estate);
 	}
 }
@@ -3214,7 +3214,7 @@ move_plain_tuple(Relation rel,
 	if (ec->resultRelInfo->ri_NumIndices > 0)
 	{
 		ExecStoreTuple(&newtup, ec->slot, InvalidBuffer, false);
-		ExecInsertIndexTuples(ec->slot, &(newtup.t_self), ec->estate, true);
+		ExecInsertIndexTuples(ec->resultRelInfo, ec->slot, &(newtup.t_self), ec->estate, true);
 		ResetPerTupleExprContext(ec->estate);
 	}
 }
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index edb6ae7..0908803 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -394,6 +394,7 @@ DefineView(ViewStmt *stmt, const char *queryString)
 	Query	   *viewParse;
 	Oid			viewOid;
 	RangeVar   *view;
+	ListCell   *lc;
 
 	/*
 	 * Run parse analysis to convert the raw parse tree to a Query.  Note this
@@ -412,6 +413,18 @@ DefineView(ViewStmt *stmt, const char *queryString)
 		viewParse->commandType != CMD_SELECT)
 		elog(ERROR, "unexpected parse analysis result");
 
+	/* .. but it doesn't check for DML inside CTEs */
+	foreach(lc, viewParse->cteList)
+	{
+		CommonTableExpr		*cte;
+
+		cte = (CommonTableExpr *) lfirst(lc);
+		if (((Query *) cte->ctequery)->commandType != CMD_SELECT)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("INSERT/UPDATE/DELETE inside a CTE not allowed in a view definition")));
+	}
+
 	/*
 	 * If a list of column names was given, run through and insert these into
 	 * the actual query tree. - thomas 2000-03-08
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 1383123..3247c5c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -924,16 +924,6 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-
-	/*
-	 * If there are indices on the result relation, open them and save
-	 * descriptors in the result relation info, so that we can add new index
-	 * entries for the tuples we add/update.  We need not do this for a
-	 * DELETE, however, since deletion doesn't affect indexes.
-	 */
-	if (resultRelationDesc->rd_rel->relhasindex &&
-		operation != CMD_DELETE)
-		ExecOpenIndices(resultRelInfo);
 }
 
 /*
@@ -1174,6 +1164,88 @@ ExecutePlan(EState *estate,
 	 */
 	estate->es_direction = direction;
 
+	/* Process top-level CTEs in case they have writes inside */
+	if (estate->es_plannedstmt->hasWritableCtes)
+	{
+		ListCell *lc;
+
+		foreach(lc, estate->es_plannedstmt->planTree->initPlan)
+		{
+			SubPlan *sp;
+			int cte_param_id;
+			ParamExecData* prmdata;
+			CteScanState *leader;
+
+			sp = (SubPlan *) lfirst(lc);
+			if (sp->subLinkType != CTE_SUBLINK)
+				continue;
+
+			cte_param_id = linitial_int(sp->setParam);
+			prmdata = &(estate->es_param_exec_vals[cte_param_id]);
+			leader = (CteScanState *) DatumGetPointer(prmdata->value);
+
+
+			/*
+			 * bump CID.
+			 *
+			 * We're currently relying on the fact that there can only be
+			 * a SELECT or VALUES as the top-level statement.
+			 *
+			 * XXX we should probably update the snapshot a bit differently
+			 */
+			CommandCounterIncrement();
+			estate->es_output_cid = GetCurrentCommandId(true);
+			estate->es_snapshot->curcid = estate->es_output_cid;
+
+			/*
+			 * If there's no leader, the CTE isn't referenced anywhere
+			 * so we can just go ahead and scan the plan
+			 */
+			if (!leader)
+			{
+				TupleTableSlot *slot;
+				PlanState *ps = (PlanState *) list_nth(estate->es_subplanstates,
+													   sp->plan_id - 1);
+
+				Assert(IsA(ps, ModifyTableState));
+
+				/*
+				 * We might have a RETURNING here, which means that
+				 * we have to loop until the plan returns NULL.
+				 */
+				for (;;)
+				{
+					slot = ExecProcNode(ps);
+					if (TupIsNull(slot))
+						break;
+				}
+			}
+			else
+			{
+				TupleTableSlot* slot;
+				PlanState *ps = (PlanState *) list_nth(estate->es_subplanstates,
+													   sp->plan_id - 1);
+
+				/* Regular CTE, ignore */
+				if (!IsA(ps, ModifyTableState))
+					continue;
+
+				/*
+				 * Scan through the leader CTE so the RETURNING tuples are
+				 * stored into the tuple store.
+				 */
+				for (;;)
+				{
+					slot = ExecProcNode((PlanState *) leader);
+					if (TupIsNull(slot))
+						break;
+				}
+
+				ExecReScan((PlanState *) leader, NULL);
+			}
+		}
+	}
+
 	/*
 	 * Loop until we've processed the proper number of tuples from the plan.
 	 */
@@ -1943,7 +2015,8 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
 	 * ExecInitSubPlan expects to be able to find these entries.
 	 * Some of the SubPlans might not be used in the part of the plan tree
 	 * we intend to run, but since it's not easy to tell which, we just
-	 * initialize them all.
+	 * initialize them all.  However, we will never run ModifyTable nodes in
+	 * EvalPlanQual() so don't initialize them.
 	 */
 	Assert(estate->es_subplanstates == NIL);
 	foreach(l, parentestate->es_plannedstmt->subplans)
@@ -1951,7 +2024,11 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
 		Plan	   *subplan = (Plan *) lfirst(l);
 		PlanState  *subplanstate;
 
-		subplanstate = ExecInitNode(subplan, estate, 0);
+		/* Don't initialize ModifyTable subplans. */
+		if (IsA(subplan, ModifyTable))
+			subplanstate = NULL;
+		else
+			subplanstate = ExecInitNode(subplan, estate, 0);
 
 		estate->es_subplanstates = lappend(estate->es_subplanstates,
 										   subplanstate);
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index b968473..44f7a47 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -968,13 +968,13 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
  * ----------------------------------------------------------------
  */
 List *
-ExecInsertIndexTuples(TupleTableSlot *slot,
+ExecInsertIndexTuples(ResultRelInfo* resultRelInfo,
+					  TupleTableSlot *slot,
 					  ItemPointer tupleid,
 					  EState *estate,
 					  bool is_vacuum_full)
 {
 	List	   *result = NIL;
-	ResultRelInfo *resultRelInfo;
 	int			i;
 	int			numIndices;
 	RelationPtr relationDescs;
@@ -987,7 +987,6 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 	/*
 	 * Get information from the result relation info structure.
 	 */
-	resultRelInfo = estate->es_result_relation_info;
 	numIndices = resultRelInfo->ri_NumIndices;
 	relationDescs = resultRelInfo->ri_IndexRelationDescs;
 	indexInfoArray = resultRelInfo->ri_IndexRelationInfo;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a4828ac..61f5026 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -158,12 +158,12 @@ ExecProcessReturning(ProjectionInfo *projectReturning,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecInsert(TupleTableSlot *slot,
+ExecInsert(ResultRelInfo *resultRelInfo,
+		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
 		   EState *estate)
 {
 	HeapTuple	tuple;
-	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -177,7 +177,6 @@ ExecInsert(TupleTableSlot *slot,
 	/*
 	 * get information on the (current) result relation
 	 */
-	resultRelInfo = estate->es_result_relation_info;
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -247,7 +246,8 @@ ExecInsert(TupleTableSlot *slot,
 	 * insert index entries for tuple
 	 */
 	if (resultRelInfo->ri_NumIndices > 0)
-		recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+		recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
+											   slot, &(tuple->t_self),
 											   estate, false);
 
 	/* AFTER ROW INSERT Triggers */
@@ -271,12 +271,12 @@ ExecInsert(TupleTableSlot *slot,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecDelete(ItemPointer tupleid,
+ExecDelete(ResultRelInfo *resultRelInfo,
+		   ItemPointer tupleid,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate)
 {
-	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	HTSU_Result result;
 	ItemPointerData update_ctid;
@@ -285,7 +285,6 @@ ExecDelete(ItemPointer tupleid,
 	/*
 	 * get information on the (current) result relation
 	 */
-	resultRelInfo = estate->es_result_relation_info;
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/* BEFORE ROW DELETE Triggers */
@@ -414,14 +413,14 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(ResultRelInfo *resultRelInfo,
+		   ItemPointer tupleid,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate)
 {
 	HeapTuple	tuple;
-	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	HTSU_Result result;
 	ItemPointerData update_ctid;
@@ -443,7 +442,6 @@ ExecUpdate(ItemPointer tupleid,
 	/*
 	 * get information on the (current) result relation
 	 */
-	resultRelInfo = estate->es_result_relation_info;
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/* BEFORE ROW UPDATE Triggers */
@@ -561,7 +559,8 @@ lreplace:;
 	 * If it's a HOT update, we mustn't insert new index entries.
 	 */
 	if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
-		recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+		recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
+											   slot, &(tuple->t_self),
 											   estate, false);
 
 	/* AFTER ROW UPDATE Triggers */
@@ -587,15 +586,15 @@ fireBSTriggers(ModifyTableState *node)
 	{
 		case CMD_INSERT:
 			ExecBSInsertTriggers(node->ps.state,
-								 node->ps.state->es_result_relations);
+								 node->resultRelInfo);
 			break;
 		case CMD_UPDATE:
 			ExecBSUpdateTriggers(node->ps.state,
-								 node->ps.state->es_result_relations);
+								 node->resultRelInfo);
 			break;
 		case CMD_DELETE:
 			ExecBSDeleteTriggers(node->ps.state,
-								 node->ps.state->es_result_relations);
+								 node->resultRelInfo);
 			break;
 		default:
 			elog(ERROR, "unknown operation");
@@ -613,15 +612,15 @@ fireASTriggers(ModifyTableState *node)
 	{
 		case CMD_INSERT:
 			ExecASInsertTriggers(node->ps.state,
-								 node->ps.state->es_result_relations);
+								 node->resultRelInfo);
 			break;
 		case CMD_UPDATE:
 			ExecASUpdateTriggers(node->ps.state,
-								 node->ps.state->es_result_relations);
+								 node->resultRelInfo);
 			break;
 		case CMD_DELETE:
 			ExecASDeleteTriggers(node->ps.state,
-								 node->ps.state->es_result_relations);
+								 node->resultRelInfo);
 			break;
 		default:
 			elog(ERROR, "unknown operation");
@@ -643,6 +642,7 @@ ExecModifyTable(ModifyTableState *node)
 	EState *estate = node->ps.state;
 	CmdType operation = node->operation;
 	PlanState *subplanstate;
+	ResultRelInfo *resultRelInfo;
 	JunkFilter *junkfilter;
 	TupleTableSlot *slot;
 	TupleTableSlot *planSlot;
@@ -658,17 +658,10 @@ ExecModifyTable(ModifyTableState *node)
 		node->fireBSTriggers = false;
 	}
 
-	/*
-	 * es_result_relation_info must point to the currently active result
-	 * relation.  (Note we assume that ModifyTable nodes can't be nested.)
-	 * We want it to be NULL whenever we're not within ModifyTable, though.
-	 */
-	estate->es_result_relation_info =
-		estate->es_result_relations + node->mt_whichplan;
-
 	/* Preload local variables */
 	subplanstate = node->mt_plans[node->mt_whichplan];
-	junkfilter = estate->es_result_relation_info->ri_junkFilter;
+	resultRelInfo = node->resultRelInfo + node->mt_whichplan;
+	junkfilter = resultRelInfo->ri_junkFilter;
 
 	/*
 	 * Fetch rows from subplan(s), and execute the required table modification
@@ -684,9 +677,9 @@ ExecModifyTable(ModifyTableState *node)
 			node->mt_whichplan++;
 			if (node->mt_whichplan < node->mt_nplans)
 			{
-				estate->es_result_relation_info++;
 				subplanstate = node->mt_plans[node->mt_whichplan];
-				junkfilter = estate->es_result_relation_info->ri_junkFilter;
+				resultRelInfo = node->resultRelInfo + node->mt_whichplan;
+				junkfilter = resultRelInfo->ri_junkFilter;
 				EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan);
 				continue;
 			}
@@ -728,14 +721,17 @@ ExecModifyTable(ModifyTableState *node)
 		switch (operation)
 		{
 			case CMD_INSERT:
-				slot = ExecInsert(slot, planSlot, estate);
+				slot = ExecInsert(resultRelInfo,
+								  slot, planSlot, estate);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(tupleid, slot, planSlot,
+				slot = ExecUpdate(resultRelInfo,
+								  tupleid, slot, planSlot,
 								  &node->mt_epqstate, estate);
 				break;
 			case CMD_DELETE:
-				slot = ExecDelete(tupleid, planSlot,
+				slot = ExecDelete(resultRelInfo,
+								  tupleid, planSlot,
 								  &node->mt_epqstate, estate);
 				break;
 			default:
@@ -748,15 +744,9 @@ ExecModifyTable(ModifyTableState *node)
 		 * the work on next call.
 		 */
 		if (slot)
-		{
-			estate->es_result_relation_info = NULL;
 			return slot;
-		}
 	}
 
-	/* Reset es_result_relation_info before exiting */
-	estate->es_result_relation_info = NULL;
-
 	/*
 	 * We're done, but fire AFTER STATEMENT triggers before exiting.
 	 */
@@ -803,25 +793,39 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
 	mtstate->mt_nplans = nplans;
 	mtstate->operation = operation;
+	mtstate->resultRelIndex = node->resultRelIndex;
+	mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
+
 	/* 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 */
-
 	/*
 	 * call ExecInitNode on each of the plans to be executed and save the
 	 * results into the array "mt_plans".  Note we *must* set
 	 * estate->es_result_relation_info correctly while we initialize each
 	 * sub-plan; ExecContextForcesOids depends on that!
 	 */
-	estate->es_result_relation_info = estate->es_result_relations;
 	i = 0;
+	resultRelInfo = mtstate->resultRelInfo;
 	foreach(l, node->plans)
 	{
 		subplan = (Plan *) lfirst(l);
+
+		/*
+		 * If there are indices on the result relation, open them and save
+		 * descriptors in the result relation info, so that we can add new index
+		 * entries for the tuples we add/update.  We need not do this for a
+		 * DELETE, however, since deletion doesn't affect indexes.
+		 */
+		if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+			operation != CMD_DELETE)
+			ExecOpenIndices(resultRelInfo);
+
+		estate->es_result_relation_info = resultRelInfo;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
-		estate->es_result_relation_info++;
+
+		resultRelInfo++;
 		i++;
 	}
 	estate->es_result_relation_info = NULL;
@@ -858,8 +862,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		/*
 		 * Build a projection for each result rel.
 		 */
-		Assert(list_length(node->returningLists) == estate->es_num_result_relations);
-		resultRelInfo = estate->es_result_relations;
+		resultRelInfo = mtstate->resultRelInfo;
 		foreach(l, node->returningLists)
 		{
 			List	   *rlist = (List *) lfirst(l);
@@ -958,7 +961,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 		if (junk_filter_needed)
 		{
-			resultRelInfo = estate->es_result_relations;
+			resultRelInfo = mtstate->resultRelInfo;
 			for (i = 0; i < nplans; i++)
 			{
 				JunkFilter *j;
@@ -987,7 +990,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		else
 		{
 			if (operation == CMD_INSERT)
-				ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc,
+				ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
 									subplan->targetlist);
 		}
 	}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a30e685..bf63570 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -84,6 +84,7 @@ _copyPlannedStmt(PlannedStmt *from)
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_NODE_FIELD(intoClause);
+	COPY_SCALAR_FIELD(hasWritableCtes);
 	COPY_NODE_FIELD(subplans);
 	COPY_BITMAPSET_FIELD(rewindPlanIDs);
 	COPY_NODE_FIELD(rowMarks);
@@ -172,6 +173,7 @@ _copyModifyTable(ModifyTable *from)
 	 */
 	COPY_SCALAR_FIELD(operation);
 	COPY_NODE_FIELD(resultRelations);
+	COPY_SCALAR_FIELD(resultRelIndex);
 	COPY_NODE_FIELD(plans);
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(rowMarks);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index b40db0b..5412e01 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2383,6 +2383,50 @@ bool
 					return true;
 			}
 			break;
+		case T_InsertStmt:
+			{
+				InsertStmt *stmt = (InsertStmt *) node;
+
+				if (walker(stmt->relation, context))
+					return true;
+				if (walker(stmt->cols, context))
+					return true;
+				if (walker(stmt->selectStmt, context))
+					return true;
+				if (walker(stmt->returningList, context))
+					return true;
+			}
+			break;
+		case T_UpdateStmt:
+			{
+				UpdateStmt *stmt = (UpdateStmt *) node;
+
+				if (walker(stmt->relation, context))
+					return true;
+				if (walker(stmt->targetList, context))
+					return true;
+				if (walker(stmt->whereClause, context))
+					return true;
+				if (walker(stmt->fromClause, context))
+					return true;
+				if (walker(stmt->returningList, context))
+					return true;
+			}
+			break;
+		case T_DeleteStmt:
+			{
+				DeleteStmt *stmt = (DeleteStmt *) node;
+
+				if (walker(stmt->relation, context))
+					return true;
+				if (walker(stmt->usingClause, context))
+					return true;
+				if (walker(stmt->whereClause, context))
+					return true;
+				if (walker(stmt->returningList, context))
+					return true;
+			}
+			break;
 		case T_A_Expr:
 			{
 				A_Expr	   *expr = (A_Expr *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 7a85f92..b981a38 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -327,6 +327,7 @@ _outModifyTable(StringInfo str, ModifyTable *node)
 
 	WRITE_ENUM_FIELD(operation, CmdType);
 	WRITE_NODE_FIELD(resultRelations);
+	WRITE_INT_FIELD(resultRelIndex);
 	WRITE_NODE_FIELD(plans);
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(rowMarks);
@@ -1529,6 +1530,7 @@ _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
 	WRITE_NODE_FIELD(finalrowmarks);
 	WRITE_NODE_FIELD(relationOids);
 	WRITE_NODE_FIELD(invalItems);
+	WRITE_NODE_FIELD(resultRelations);
 	WRITE_UINT_FIELD(lastPHId);
 	WRITE_BOOL_FIELD(transientPlan);
 }
@@ -1543,7 +1545,6 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node)
 	WRITE_NODE_FIELD(glob);
 	WRITE_UINT_FIELD(query_level);
 	WRITE_NODE_FIELD(join_rel_list);
-	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(init_plans);
 	WRITE_NODE_FIELD(cte_plan_ids);
 	WRITE_NODE_FIELD(eq_classes);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 331963f..4402092 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -3759,10 +3759,6 @@ make_modifytable(CmdType operation, List *resultRelations,
 	double		total_size;
 	ListCell   *subnode;
 
-	Assert(list_length(resultRelations) == list_length(subplans));
-	Assert(returningLists == NIL ||
-		   list_length(resultRelations) == list_length(returningLists));
-
 	/*
 	 * Compute cost as sum of subplan costs.
 	 */
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 7f2f0c6..b352956 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -160,6 +160,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->finalrowmarks = NIL;
 	glob->relationOids = NIL;
 	glob->invalItems = NIL;
+	glob->hasWritableCtes = false;
+	glob->resultRelations = NIL;
 	glob->lastPHId = 0;
 	glob->transientPlan = false;
 
@@ -237,9 +239,10 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->transientPlan = glob->transientPlan;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
-	result->resultRelations = root->resultRelations;
+	result->resultRelations = glob->resultRelations;
 	result->utilityStmt = parse->utilityStmt;
 	result->intoClause = parse->intoClause;
+	result->hasWritableCtes = glob->hasWritableCtes;
 	result->subplans = glob->subplans;
 	result->rewindPlanIDs = glob->rewindPlanIDs;
 	result->rowMarks = glob->finalrowmarks;
@@ -541,7 +544,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 				rowMarks = root->rowMarks;
 
 			plan = (Plan *) make_modifytable(parse->commandType,
-											 copyObject(root->resultRelations),
+											 list_make1_int(parse->resultRelation),
 											 list_make1(plan),
 											 returningLists,
 											 rowMarks,
@@ -706,13 +709,13 @@ inheritance_planner(PlannerInfo *root)
 	Query	   *parse = root->parse;
 	int			parentRTindex = parse->resultRelation;
 	List	   *subplans = NIL;
-	List	   *resultRelations = NIL;
 	List	   *returningLists = NIL;
 	List	   *rtable = NIL;
 	List	   *rowMarks;
 	List	   *tlist;
 	PlannerInfo subroot;
 	ListCell   *l;
+	List	   *resultRelations = NIL;
 
 	foreach(l, root->append_rel_list)
 	{
@@ -772,8 +775,6 @@ inheritance_planner(PlannerInfo *root)
 		}
 	}
 
-	root->resultRelations = resultRelations;
-
 	/* Mark result as unordered (probably unnecessary) */
 	root->query_pathkeys = NIL;
 
@@ -783,7 +784,6 @@ inheritance_planner(PlannerInfo *root)
 	 */
 	if (subplans == NIL)
 	{
-		root->resultRelations = list_make1_int(parentRTindex);
 		/* although dummy, it must have a valid tlist for executor */
 		tlist = preprocess_targetlist(root, parse->targetList);
 		return (Plan *) make_result(root,
@@ -818,7 +818,7 @@ inheritance_planner(PlannerInfo *root)
 
 	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
 	return (Plan *) make_modifytable(parse->commandType,
-									 copyObject(root->resultRelations),
+									 resultRelations,
 									 subplans, 
 									 returningLists,
 									 rowMarks,
@@ -1667,12 +1667,6 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 										  count_est);
 	}
 
-	/* Compute result-relations list if needed */
-	if (parse->resultRelation)
-		root->resultRelations = list_make1_int(parse->resultRelation);
-	else
-		root->resultRelations = NIL;
-
 	/*
 	 * Return the actual output ordering in query_pathkeys for possible use by
 	 * an outer query level.
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 34c3ea6..47d2c00 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -516,6 +516,10 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
 											  (Plan *) lfirst(l),
 											  rtoffset);
 				}
+
+				splan->resultRelIndex = list_length(glob->resultRelations);
+				glob->resultRelations = list_concat(glob->resultRelations,
+													splan->resultRelations);
 			}
 			break;
 		case T_Append:
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 291ec99..bd61cc6 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -873,16 +873,33 @@ SS_process_ctes(PlannerInfo *root)
 		Bitmapset  *tmpset;
 		int			paramid;
 		Param	   *prm;
+		CmdType		cmdType = ((Query *) cte->ctequery)->commandType;
 
 		/*
-		 * Ignore CTEs that are not actually referenced anywhere.
+		 * Ignore SELECT CTEs that are not actually referenced anywhere.
 		 */
-		if (cte->cterefcount == 0)
+		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
 		}
+		else if (cmdType != CMD_SELECT)
+		{
+			/* We don't know reference counts until here */
+			if (cte->cterefcount > 0 &&
+				((Query *) cte->ctequery)->returningList == NIL)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("INSERT/UPDATE/DELETE without RETURNING is only allowed inside a non-referenced CTE")));
+			}
+
+			if (root->query_level > 1)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("INSERT/UPDATE/DELETE inside a CTE is only allowed on the top level")));
+		}
 
 		/*
 		 * Copy the source Query node.	Probably not necessary, but let's keep
@@ -899,6 +916,9 @@ SS_process_ctes(PlannerInfo *root)
 								cte->cterecursive, 0.0,
 								&subroot);
 
+		if (subroot->parse->commandType != CMD_SELECT)
+			root->glob->hasWritableCtes = true;
+
 		/*
 		 * Make a SubPlan node for it.	This is just enough unlike
 		 * build_subplan that we can't share code.
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4ed5b06..cc748c7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -7374,6 +7374,33 @@ common_table_expr:  name opt_name_list AS select_with_parens
 				n->location = @1;
 				$$ = (Node *) n;
 			}
+        | name opt_name_list AS '(' InsertStmt ')'
+			{
+				CommonTableExpr *n = makeNode(CommonTableExpr);
+				n->ctename = $1;
+				n->aliascolnames = $2;
+				n->ctequery = $5;
+				n->location = @1;
+				$$ = (Node *) n;
+			}
+        | name opt_name_list AS '(' UpdateStmt ')'
+			{
+				CommonTableExpr *n = makeNode(CommonTableExpr);
+				n->ctename = $1;
+				n->aliascolnames = $2;
+				n->ctequery = $5;
+				n->location = @1;
+				$$ = (Node *) n;
+			}
+        | name opt_name_list AS '(' DeleteStmt ')'
+			{
+				CommonTableExpr *n = makeNode(CommonTableExpr);
+				n->ctename = $1;
+				n->aliascolnames = $2;
+				n->ctequery = $5;
+				n->location = @1;
+				$$ = (Node *) n;
+			}
 		;
 
 into_clause:
diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c
index e2fcc60..f251ec8 100644
--- a/src/backend/parser/parse_cte.c
+++ b/src/backend/parser/parse_cte.c
@@ -18,6 +18,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_cte.h"
+#include "nodes/plannodes.h"
 #include "utils/builtins.h"
 
 
@@ -225,22 +226,25 @@ static void
 analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
 {
 	Query	   *query;
-
-	/* Analysis not done already */
-	Assert(IsA(cte->ctequery, SelectStmt));
+	List	   *cteList;
 
 	query = parse_sub_analyze(cte->ctequery, pstate, cte, false);
 	cte->ctequery = (Node *) query;
 
+	if (query->commandType == CMD_SELECT)
+		cteList = query->targetList;
+	else
+		cteList = query->returningList;
+
 	/*
 	 * Check that we got something reasonable.	Many of these conditions are
 	 * impossible given restrictions of the grammar, but check 'em anyway.
-	 * (These are the same checks as in transformRangeSubselect.)
+	 * Note, however, that we can't yet decice whether to allow
+	 * INSERT/UPDATE/DELETE without a RETURNING clause or not because we don't
+	 * know the refcount.
 	 */
-	if (!IsA(query, Query) ||
-		query->commandType != CMD_SELECT ||
-		query->utilityStmt != NULL)
-		elog(ERROR, "unexpected non-SELECT command in subquery in WITH");
+	Assert(IsA(query, Query) && query->utilityStmt == NULL);
+
 	if (query->intoClause)
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
@@ -251,7 +255,7 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
 	if (!cte->cterecursive)
 	{
 		/* Compute the output column names/types if not done yet */
-		analyzeCTETargetList(pstate, cte, query->targetList);
+		analyzeCTETargetList(pstate, cte, cteList);
 	}
 	else
 	{
@@ -269,7 +273,7 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
 		lctyp = list_head(cte->ctecoltypes);
 		lctypmod = list_head(cte->ctecoltypmods);
 		varattno = 0;
-		foreach(lctlist, query->targetList)
+		foreach(lctlist, cteList)
 		{
 			TargetEntry *te = (TargetEntry *) lfirst(lctlist);
 			Node	   *texpr;
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 7140758..24a2cc2 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -24,6 +24,7 @@
 #include "funcapi.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/plannodes.h"
 #include "parser/parsetree.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_type.h"
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 6883dc3..e09875d 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -314,10 +314,20 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
 			{
 				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
 				TargetEntry *ste;
+				List		*cteList;
+				Query		*ctequery;
 
 				/* should be analyzed by now */
 				Assert(IsA(cte->ctequery, Query));
-				ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
+
+				ctequery = (Query *) cte->ctequery;
+
+				if (ctequery->commandType == CMD_SELECT)
+					cteList = ctequery->targetList;
+				else
+					cteList = ctequery->returningList;
+
+				ste = get_tle_by_resno(cteList,
 									   attnum);
 				if (ste == NULL || ste->resjunk)
 					elog(ERROR, "subquery %s does not have attribute %d",
@@ -1345,11 +1355,20 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
 			{
 				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
 				TargetEntry *ste;
+				List		*cteList;
+				Query		*ctequery;
 
 				/* should be analyzed by now */
 				Assert(IsA(cte->ctequery, Query));
-				ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
-									   attnum);
+
+				ctequery = (Query *) cte->ctequery;
+
+				if (ctequery->commandType == CMD_SELECT)
+					cteList = ctequery->targetList;
+				else
+					cteList = ctequery->returningList;
+
+				ste = get_tle_by_resno(cteList, attnum);
 				if (ste == NULL || ste->resjunk)
 					elog(ERROR, "subquery %s does not have attribute %d",
 						 rte->eref->aliasname, attnum);
@@ -1372,7 +1391,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
 						 levelsup++)
 						pstate = pstate->parentParseState;
 					mypstate.parentParseState = pstate;
-					mypstate.p_rtable = ((Query *) cte->ctequery)->rtable;
+					mypstate.p_rtable = ctequery->rtable;
 					/* don't bother filling the rest of the fake pstate */
 
 					return expandRecordVariable(&mypstate, (Var *) expr, 0);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 7af481d..2cc05fb 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1632,6 +1632,10 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	bool		returning = false;
 	Query	   *qual_product = NULL;
 	List	   *rewritten = NIL;
+	ListCell	*lc;
+	CommonTableExpr	*cte;
+	Query		*ctequery;
+	List		*newstuff;
 
 	/*
 	 * If the statement is an update, insert or delete - fire rules on it.
@@ -1749,7 +1753,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 				foreach(n, product_queries)
 				{
 					Query	   *pt = (Query *) lfirst(n);
-					List	   *newstuff;
 
 					newstuff = RewriteQuery(pt, rewrite_events);
 					rewritten = list_concat(rewritten, newstuff);
@@ -1804,6 +1807,55 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	}
 
 	/*
+	 * Rewrite DML statements inside CTEs.  If there are any
+	 * DO ALSO rules, they are added to the top level (there
+	 * won't be any non-top-level CTEs with DML).
+	 */
+	foreach(lc, parsetree->cteList)
+	{
+		cte = lfirst(lc);
+
+		ctequery = (Query *) cte->ctequery;
+
+		if (ctequery->commandType == CMD_SELECT)
+			continue;
+
+		newstuff = RewriteQuery(ctequery, NIL);
+
+		/*
+		 * For UPDATE and DELETE, the actual query is
+		 * added to the end of the list.
+		 */
+		if (list_length(newstuff) > 1 &&
+			ctequery->commandType != CMD_INSERT)
+		{
+			ListCell	*lc;
+			int n = 1;
+
+			foreach(lc, newstuff)
+			{
+				/*
+				 * If this is the last one, don't add it to the results.
+				 * Instead, update the query inside the CTE.
+				 */
+				if (n == list_length(newstuff))
+					cte->ctequery = (Node *) lfirst(lc);
+				else
+					rewritten = lcons((void *) lfirst(lc), rewritten);
+					
+				n++;
+			}
+
+		}
+		else
+		{
+			cte->ctequery = (Node *) linitial(newstuff);
+			rewritten = list_concat(rewritten,
+							  list_delete_first(newstuff));
+		}
+	}
+
+	/*
 	 * For INSERTs, the original query is done first; for UPDATE/DELETE, it is
 	 * done last.  This is needed because update and delete rule actions might
 	 * not do anything if they are invoked after the update or delete is
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 6f46a29..1c6c56e 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -293,6 +293,7 @@ ChoosePortalStrategy(List *stmts)
 			if (pstmt->canSetTag)
 			{
 				if (pstmt->commandType == CMD_SELECT &&
+					pstmt->hasWritableCtes == false &&
 					pstmt->utilityStmt == NULL &&
 					pstmt->intoClause == NULL)
 					return PORTAL_ONE_SELECT;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 42b1c6a..be7a1b5 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3858,9 +3858,16 @@ get_name_for_var_field(Var *var, int fieldno,
 				}
 				if (lc != NULL)
 				{
-					Query	   *ctequery = (Query *) cte->ctequery;
-					TargetEntry *ste = get_tle_by_resno(ctequery->targetList,
-														attnum);
+					Query		*ctequery = (Query *) cte->ctequery;
+					List		*ctelist;
+					TargetEntry	*ste;
+
+					if (ctequery->commandType != CMD_SELECT)
+						ctelist = ctequery->returningList;
+					else
+						ctelist = ctequery->targetList;
+
+					ste = get_tle_by_resno(ctelist, attnum);
 
 					if (ste == NULL || ste->resjunk)
 						elog(ERROR, "subquery %s does not have attribute %d",
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d367b2a..cccc8f6 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -319,7 +319,8 @@ extern void ExecCloseScanRelation(Relation scanrel);
 
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
-extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
+extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
+					  TupleTableSlot *slot, ItemPointer tupleid,
 					  EState *estate, bool is_vacuum_full);
 
 extern void RegisterExprContextCallback(ExprContext *econtext,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9acd5ec..2c84493 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1021,6 +1021,8 @@ typedef struct ModifyTableState
 	PlanState	  **mt_plans;		/* subplans (one per target rel) */
 	int				mt_nplans;		/* number of plans in the array */
 	int				mt_whichplan;	/* which one is being executed (0..n-1) */
+	int				resultRelIndex;
+	ResultRelInfo  *resultRelInfo;
 	EPQState		mt_epqstate;	/* for evaluating EvalPlanQual rechecks */
 	bool			fireBSTriggers;	/* do we need to fire stmt triggers? */
 } ModifyTableState;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index d8a89fa..5b67b0a 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -55,6 +55,8 @@ typedef struct PlannedStmt
 
 	IntoClause *intoClause;		/* target for SELECT INTO / CREATE TABLE AS */
 
+	bool		hasWritableCtes;
+
 	List	   *subplans;		/* Plan trees for SubPlan expressions */
 
 	Bitmapset  *rewindPlanIDs;	/* indices of subplans that require REWIND */
@@ -165,6 +167,7 @@ typedef struct ModifyTable
 	Plan		plan;
 	CmdType		operation;			/* INSERT, UPDATE, or DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes */
+	int			resultRelIndex;
 	List	   *plans;				/* plan(s) producing source data */
 	List	   *returningLists;		/* per-target-table RETURNING tlists */
 	List	   *rowMarks;			/* PlanRowMarks (non-locking only) */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 4c82106..5f13076 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -80,6 +80,10 @@ typedef struct PlannerGlobal
 
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
+	bool		hasWritableCtes;/* is there an (INSERT|UPDATE|DELETE) .. RETURNING inside a CTE? */
+
+	List	   *resultRelations;/* list of result relations */
+
 	Index		lastPHId;		/* highest PlaceHolderVar ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
@@ -142,8 +146,6 @@ typedef struct PlannerInfo
 	List	   *join_rel_list;	/* list of join-relation RelOptInfos */
 	struct HTAB *join_rel_hash; /* optional hashtable for join relations */
 
-	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
-
 	List	   *init_plans;		/* init SubPlans for query */
 
 	List	   *cte_plan_ids;	/* per-CTE-item list of subplan IDs */
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index a3e94e9..4cfb569 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -1026,3 +1026,85 @@ SELECT * FROM t;
  10
 (55 rows)
 
+--
+-- Writeable CTEs with RETURNING
+--
+WITH t AS (
+    INSERT INTO y
+    VALUES
+        (11),
+        (12),
+        (13),
+        (14),
+        (15),
+        (16),
+        (17),
+        (18),
+        (19),
+        (20)
+    RETURNING *
+)
+SELECT * FROM t;
+ a  
+----
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+(10 rows)
+
+WITH t AS (
+    UPDATE y
+    SET a=a+1
+    RETURNING *
+)
+SELECT * FROM t;
+ a  
+----
+  2
+  3
+  4
+  5
+  6
+  7
+  8
+  9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+(20 rows)
+
+WITH t AS (
+    DELETE FROM y
+    WHERE a <= 10
+    RETURNING *
+)
+SELECT * FROM t;
+ a  
+----
+  2
+  3
+  4
+  5
+  6
+  7
+  8
+  9
+ 10
+(9 rows)
+
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index 2cbaa42..5b35af3 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -500,3 +500,38 @@ WITH RECURSIVE t(j) AS (
     SELECT j+1 FROM t WHERE j < 10
 )
 SELECT * FROM t;
+
+--
+-- Writeable CTEs with RETURNING
+--
+
+WITH t AS (
+    INSERT INTO y
+    VALUES
+        (11),
+        (12),
+        (13),
+        (14),
+        (15),
+        (16),
+        (17),
+        (18),
+        (19),
+        (20)
+    RETURNING *
+)
+SELECT * FROM t;
+
+WITH t AS (
+    UPDATE y
+    SET a=a+1
+    RETURNING *
+)
+SELECT * FROM t;
+
+WITH t AS (
+    DELETE FROM y
+    WHERE a <= 10
+    RETURNING *
+)
+SELECT * FROM t;