Hi,

Attached is a WIP patch which implements writeable CTEs.  This patch has
some defects I'll be discussing below.  Also, I haven't implemented the
grammar changes for using WITH ( .. RETURNING ) in non-SELECT queries
yet.

What's not obvious from the patch:
      - estate->es_result_relation_info is currently only set during
        EvalPlanQual().  ModifyTable nodes have an array of
        ResultRelInfos they will be operating on.  That array is part of
        estate->es_result_relations.
      - I removed resultRelations from PlannerInfo completely because I
        didn't find use for it any more.  That list is now stored first
        in ModifyTable nodes, and then added to PlannerGlobal's
        new resultRelations list during set_plan_refs().

Currently, we don't allow DO ALSO SELECT .. rules for SELECT queries.
But with this patch you could have a top-level SELECT which results in
multiple SELECTs when the DML operations inside CTEs are rewritten.
Consider this example:

=> CREATE RULE additional_select AS ON INSERT TO foo DO ALSO SELECT *
FROM bar;

=> WITH t AS (INSERT INTO foo VALUES(0) RETURNING *) SELECT * FROM t;

INSERT INTO foo VALUES(0) is ran first, but the results of that are
ignored.  What you actually see is the output of SELECT * FROM bar which
is certainly surprising.  What do you think should happen here?
INSERT/UPDATE/DELETE works as expected; both queries are ran but you get
the output of SELECT * FROM t;

Currently we also only allow cursors for simple SELECT queries.  IMHO we
should also allow cursor for SELECT queries like the one above; the
INSERT is run to completion first, but then the user could use a cursor
to scan through the RETURNING tuples.  I haven't looked into this very
thoroughly yet, but I don't see any obvious problems.

I'd appreciate any input.

Regards,
Marko Tiikkaja

diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index b2741bc..111ed6a 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,28 @@ GROUP BY region, product;
   </para>
 
   <para>
+  <literal>WITH</literal> clauses can also have a
+  <literal>INSERT</literal>, <literal>UPDATE</literal>,
+  <literal>DELETE</literal>(each optionally with a
+  <literal>RETURNING</literal> clause) in them.  The example below
+  moves rows from the main table, foo_log into a partition,
+  foo_log_200910.
+
+<programlisting>
+WITH t AS (
+    DELETE FROM foo_log
+    WHERE
+        foo_date &gt;= '2009-10-01' AND
+        foo_date &lt;  '2009-11-01'
+    RETURNING *
+)
+INSERT INTO foo_log_200910
+SELECT * FROM t;
+</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 05d90dc..29cc9db 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/trigger.c b/src/backend/commands/trigger.c
index 756c65c..2446c6f 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2159,7 +2159,7 @@ ltrmark:;
 					TupleTableSlot *epqslot;
 
 					epqslot = EvalPlanQual(estate,
-										   relinfo->ri_RangeTableIndex,
+										   relinfo,
 										   subplanstate,
 										   &update_ctid,
 										   update_xmax);
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c9c9baa..cd63630 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -3067,7 +3067,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);
 	}
 }
@@ -3193,7 +3193,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/executor/execMain.c b/src/backend/executor/execMain.c
index 6e79405..7858f97 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -171,7 +171,8 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 		case CMD_SELECT:
 			/* SELECT INTO and SELECT FOR UPDATE/SHARE need to mark tuples */
 			if (queryDesc->plannedstmt->intoClause != NULL ||
-				queryDesc->plannedstmt->rowMarks != NIL)
+				queryDesc->plannedstmt->rowMarks != NIL ||
+				queryDesc->plannedstmt->hasWritableCtes)
 				estate->es_output_cid = GetCurrentCommandId(true);
 			break;
 
@@ -1173,6 +1174,92 @@ ExecutePlan(EState *estate,
 	 */
 	estate->es_direction = direction;
 
+	/* Process top-level CTEs in case they have writes inside */
+	{
+		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);
+			
+			/*
+			 * 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 might 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
+				 *
+				 * XXX this isn't probably the best way to determine whether there's
+				 * an INSERT/UPDATE/DELETE inside the CTE
+				 */
+				if (!IsA(ps, ModifyTableState))
+					continue;
+
+				/*
+				 * Tell the CTE leader to scan itself and then return
+				 *
+				 * XXX would there be something to gain in using a custom function
+				 * in nodeCtescan.c for this? */				
+				for (;;)
+				{
+					slot = ExecProcNode((PlanState *) leader);
+					if (TupIsNull(slot))
+						break;
+				}
+
+				ExecReScan((PlanState *) leader, NULL);
+			}
+
+			/* 
+			 * bump CID.
+			 *
+			 * 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;
+
+			/* Should we do something for crosscheck snapshot here? */
+		}
+	}
+
 	/*
 	 * Loop until we've processed the proper number of tuples from the plan.
 	 */
@@ -1230,7 +1317,6 @@ ExecutePlan(EState *estate,
 	}
 }
 
-
 /*
  * ExecRelCheck --- check that tuple meets constraints for result relation
  */
@@ -1337,7 +1423,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
  * See backend/executor/README for some info about how this works.
  *
  *	estate - executor state data
- *	rti - rangetable index of table containing tuple
+ *	resultRelInfo - ResultRelInfo of table containing tuple
  *	subplanstate - portion of plan tree that needs to be re-evaluated
  *	*tid - t_ctid from the outdated tuple (ie, next updated version)
  *	priorXmax - t_xmax from the outdated tuple
@@ -1349,15 +1435,23 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
  * NULL if we determine we shouldn't process the row.
  */
 TupleTableSlot *
-EvalPlanQual(EState *estate, Index rti,
+EvalPlanQual(EState *estate, ResultRelInfo *resultRelInfo,
 			 PlanState *subplanstate,
 			 ItemPointer tid, TransactionId priorXmax)
 {
 	TupleTableSlot *slot;
 	HeapTuple	copyTuple;
+	Index rti;
+
+	Assert(resultRelInfo != NULL);
+
+	rti = resultRelInfo->ri_RangeTableIndex;
 
 	Assert(rti != 0);
 
+	/* Set es_result_relation_info correctly for EvalPlanQualFetch() */
+	estate->es_result_relation_info = resultRelInfo;
+
 	/*
 	 * Get the updated version of the row; if fail, return NULL.
 	 */
@@ -1420,6 +1514,8 @@ EvalPlanQual(EState *estate, Index rti,
 	 */
 	EvalPlanQualPop(estate, subplanstate);
 
+	estate->es_result_relation_info = NULL;
+
 	return slot;
 }
 
@@ -1892,7 +1988,8 @@ EvalPlanQualStart(evalPlanQual *epq, EState *estate, 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(epqstate->es_subplanstates == NIL);
 	foreach(l, estate->es_plannedstmt->subplans)
@@ -1900,7 +1997,11 @@ EvalPlanQualStart(evalPlanQual *epq, EState *estate, Plan *planTree,
 		Plan	   *subplan = (Plan *) lfirst(l);
 		PlanState  *subplanstate;
 
-		subplanstate = ExecInitNode(subplan, epqstate, 0);
+		/* Don't initialize ModifyTable subplans. */
+		if (IsA(subplan, ModifyTable))
+			subplanstate = NULL;
+		else
+			subplanstate = ExecInitNode(subplan, epqstate, 0);
 
 		epqstate->es_subplanstates = lappend(epqstate->es_subplanstates,
 											 subplanstate);
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 23d987e..dbaa131 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 d623e0b..5f22416 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,
 		   PlanState *subplanstate,
 		   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 */
@@ -334,7 +333,7 @@ ldelete:;
 				TupleTableSlot *epqslot;
 
 				epqslot = EvalPlanQual(estate,
-									   resultRelInfo->ri_RangeTableIndex,
+									   resultRelInfo,
 									   subplanstate,
 									   &update_ctid,
 									   update_xmax);
@@ -413,14 +412,14 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(ResultRelInfo *resultRelInfo,
+		   ItemPointer tupleid,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
 		   PlanState *subplanstate,
 		   EState *estate)
 {
 	HeapTuple	tuple;
-	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	HTSU_Result result;
 	ItemPointerData update_ctid;
@@ -442,7 +441,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 */
@@ -520,7 +518,7 @@ lreplace:;
 				TupleTableSlot *epqslot;
 
 				epqslot = EvalPlanQual(estate,
-									   resultRelInfo->ri_RangeTableIndex,
+									   resultRelInfo,
 									   subplanstate,
 									   &update_ctid,
 									   update_xmax);
@@ -559,7 +557,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 */
@@ -585,15 +584,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");
@@ -611,15 +610,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");
@@ -641,6 +640,7 @@ ExecModifyTable(ModifyTableState *node)
 	EState *estate = node->ps.state;
 	CmdType operation = node->operation;
 	PlanState *subplanstate;
+	ResultRelInfo *resultRelInfo;
 	JunkFilter *junkfilter;
 	TupleTableSlot *slot;
 	TupleTableSlot *planSlot;
@@ -656,17 +656,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
@@ -682,9 +675,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;
 				continue;
 			}
 			else
@@ -724,14 +717,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,
 								  subplanstate, estate);
 				break;
 			case CMD_DELETE:
-				slot = ExecDelete(tupleid, planSlot,
+				slot = ExecDelete(resultRelInfo,
+								  tupleid, planSlot,
 								  subplanstate, estate);
 				break;
 			default:
@@ -744,15 +740,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.
 	 */
@@ -799,23 +789,22 @@ 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;
 	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;
 	foreach(l, node->plans)
 	{
 		subplan = (Plan *) lfirst(l);
+		estate->es_result_relation_info = mtstate->resultRelInfo + i;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
-		estate->es_result_relation_info++;
 		i++;
 	}
 	estate->es_result_relation_info = NULL;
@@ -851,8 +840,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);
@@ -919,7 +907,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;
@@ -948,7 +936,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 5b9591e..104b341 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);
 
@@ -1452,7 +1454,7 @@ _copyXmlExpr(XmlExpr *from)
 
 	return newnode;
 }
-
+	
 /*
  * _copyNullIfExpr (same as OpExpr)
  */
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 75d5be5..79eef3b 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);
 }
@@ -1511,6 +1512,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_UINT_FIELD(lastRowmarkId);
 	WRITE_BOOL_FIELD(transientPlan);
@@ -1526,7 +1528,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 2167eba..4c9a7f5 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -3757,10 +3757,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 5d3d355..328d52c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -158,6 +158,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->lastRowmarkId = 0;
 	glob->transientPlan = false;
@@ -236,9 +238,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;
@@ -535,7 +538,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 				returningLists = NIL;
 
 			plan = (Plan *) make_modifytable(parse->commandType,
-											 copyObject(root->resultRelations),
+											 list_make1_int(parse->resultRelation),
 											 list_make1(plan),
 											 returningLists);
 		}
@@ -698,12 +701,12 @@ 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	   *tlist;
 	PlannerInfo subroot;
 	ListCell   *l;
+	List	   *resultRelations = NIL;
 
 	foreach(l, root->append_rel_list)
 	{
@@ -763,8 +766,6 @@ inheritance_planner(PlannerInfo *root)
 		}
 	}
 
-	root->resultRelations = resultRelations;
-
 	/* Mark result as unordered (probably unnecessary) */
 	root->query_pathkeys = NIL;
 
@@ -774,7 +775,9 @@ inheritance_planner(PlannerInfo *root)
 	 */
 	if (subplans == NIL)
 	{
-		root->resultRelations = list_make1_int(parentRTindex);
+		/* XXX what should we do here? */
+		//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,
@@ -799,7 +802,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);
 }
@@ -1637,12 +1640,6 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 											 parse->rowMarks);
 	}
 
-	/* 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 55922f3..f9bb52f 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -498,16 +498,23 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
 				 */
 				Assert(splan->plan.qual == NIL);
 
-				foreach(l, splan->resultRelations)
-				{
-					lfirst_int(l) += rtoffset;
-				}
+				Assert(plan->lefttree == NULL && plan->righttree == NULL);
+
 				foreach(l, splan->plans)
 				{
 					lfirst(l) = set_plan_refs(glob,
 											  (Plan *) lfirst(l),
 											  rtoffset);
 				}
+
+				foreach(l, splan->resultRelations)
+				{
+					lfirst_int(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 8e077f5..cf013b1 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -868,16 +868,26 @@ 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 (cte->cterefcount > 0 &&
+				 cmdType != CMD_SELECT &&
+				 ((Query *) cte->ctequery)->returningList == NIL)
+		{
+			/* XXX Should this be in parse_relation.c? */
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("INSERT/UPDATE/DELETE without RETURNING is only allowed inside a non-referenced CTE")));
+		}
 
 		/*
 		 * Copy the source Query node.	Probably not necessary, but let's keep
@@ -894,6 +904,11 @@ SS_process_ctes(PlannerInfo *root)
 								cte->cterecursive, 0.0,
 								&subroot);
 
+		/* XXX Do we really need to know this? */
+		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 b6b32c5..07c65d3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -7371,6 +7371,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 19bfb8e..7ce5dad 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,30 @@ static void
 analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
 {
 	Query	   *query;
+	List	   *cteList;
 
 	/* Analysis not done already */
+	/*
 	Assert(IsA(cte->ctequery, SelectStmt));
+	*/
 
 	query = parse_sub_analyze(cte->ctequery, pstate, cte);
 	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 +260,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 +278,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 df546aa..ac9f137 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"
@@ -1391,8 +1392,8 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	rte->ctelevelsup = levelsup;
 
 	/* Self-reference if and only if CTE's parse analysis isn't completed */
-	rte->self_reference = !IsA(cte->ctequery, Query);
-	Assert(cte->cterecursive || !rte->self_reference);
+	rte->self_reference = !IsA(cte->ctequery, Query) && !IsA(cte->ctequery, ModifyTable);
+	Assert(cte->cterecursive || !rte->self_reference || IsA(cte->ctequery, ModifyTable));
 	/* Bump the CTE's refcount if this isn't a self-reference */
 	if (!rte->self_reference)
 		cte->cterefcount++;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index f6e2bbe..e7de319 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -310,10 +310,20 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
 			{
 				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
 				TargetEntry *ste;
+				List		*cteList;
+				Query		*query;
 
 				/* should be analyzed by now */
 				Assert(IsA(cte->ctequery, Query));
-				ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
+
+				query = (Query *) cte->ctequery;
+
+				if (query->commandType == CMD_SELECT)
+					cteList = query->targetList;
+				else
+					cteList = query->returningList;
+
+				ste = get_tle_by_resno(cteList,
 									   attnum);
 				if (ste == NULL || ste->resjunk)
 					elog(ERROR, "subquery %s does not have attribute %d",
@@ -1234,8 +1244,8 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
 
 				/* should be analyzed by now */
 				Assert(IsA(cte->ctequery, Query));
-				ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
-									   attnum);
+
+				ste = get_tle_by_resno(((Query* ) cte->ctequery)->targetList, attnum);
 				if (ste == NULL || ste->resjunk)
 					elog(ERROR, "subquery %s does not have attribute %d",
 						 rte->eref->aliasname, attnum);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 38930e1..50d21f3 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1929,6 +1929,51 @@ QueryRewrite(Query *parsetree)
 		results = lappend(results, query);
 	}
 
+	{
+		ListCell	*lc;
+		CommonTableExpr	*cte;
+		Query		*ctequery;
+		List		*rewritten;
+
+		foreach(lc, parsetree->cteList)
+		{
+			cte = lfirst(lc);
+
+			ctequery = (Query *) cte->ctequery;
+
+			if (ctequery->commandType == CMD_SELECT)
+				continue;
+
+			rewritten = QueryRewrite(ctequery);
+
+			/* 
+			 * For UPDATE and DELETE, the actual query is
+			 * added to the end of the list.
+			 */
+			if (list_length(rewritten) > 1 && ctequery->commandType != CMD_INSERT)
+			{
+				ListCell	*lc;
+				int n = 1;
+
+				foreach(lc, rewritten)
+				{
+					if (n == list_length(rewritten))
+						cte->ctequery = (Node *) lfirst(lc);
+					else
+						results = lcons((void *) lfirst(lc), results);
+
+					n++;
+				}
+			}
+			else
+			{
+				cte->ctequery = (Node *) linitial(rewritten);
+				results = list_concat(results,
+									  list_delete_first(rewritten));
+			}
+		}
+	}
+
 	/*
 	 * Step 3
 	 *
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 ea88c3b..7a7997f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3853,9 +3853,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 7dfaff0..5666137 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -166,7 +166,7 @@ extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
 extern void ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate);
-extern TupleTableSlot *EvalPlanQual(EState *estate, Index rti,
+extern TupleTableSlot *EvalPlanQual(EState *estate, ResultRelInfo *resultRelInfo,
 			 PlanState *subplanstate,
 			 ItemPointer tid, TransactionId priorXmax);
 extern HeapTuple EvalPlanQualFetch(EState *estate, Index rti,
@@ -309,7 +309,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 bd48c32..cc9bfd8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -991,6 +991,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;
 	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 9538b8f..7feb72e 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 */
 } ModifyTable;
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index f7eb915..d995d19 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 */
 
 	Index		lastRowmarkId;	/* highest RowMarkClause ID assigned */
@@ -144,8 +148,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;



-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to