Writeable CTEs

Started by Marko Tiikkajaabout 16 years ago9 messages
#1Marko Tiikkaja
marko.tiikkaja@cs.helsinki.fi
1 attachment(s)

Hi,

Attached is a patch where I've taken into account the problems Tom
pointed out in his review [1]http://archives.postgresql.org/pgsql-hackers/2009-11/msg01860.php. It still needs a bit cleaning up, but
I'm not planning on any big changes. Any feedback welcome.

One thing I only noticed now is this:

=> select * from foo;
a
---
0
(1 row)

=> with t as (delete from foo returning *)
-> insert into bar
-> select * from t;
INSERT 0 2

It correctly reports 2 affected rows (one deleted and one inserted), but
is this the answer we want? It doesn't seem all that useful to know the
total amount of affected rows.

Regards,
Marko Tiikkaja

[1]: http://archives.postgresql.org/pgsql-hackers/2009-11/msg01860.php

Attachments:

writeable4.patchtext/plain; charset=iso-8859-1; name=writeable4.patchDownload
*** a/doc/src/sgml/queries.sgml
--- b/doc/src/sgml/queries.sgml
***************
*** 1529,1538 **** 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:
  
  <programlisting>
  WITH regional_sales AS (
--- 1529,1539 ----
    </indexterm>
  
    <para>
!    <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,1565 **** GROUP BY region, product;
--- 1561,1588 ----
    </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 *
+      )
+ INSERT INTO foo_log_200910
+ SELECT * FROM rows
+ </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
*** a/doc/src/sgml/ref/delete.sgml
--- b/doc/src/sgml/ref/delete.sgml
***************
*** 21,26 **** PostgreSQL documentation
--- 21,27 ----
  
   <refsynopsisdiv>
  <synopsis>
+ [ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
  DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
      [ USING <replaceable class="PARAMETER">using_list</replaceable> ]
      [ WHERE <replaceable class="PARAMETER">condition</replaceable> | WHERE CURRENT OF <replaceable class="PARAMETER">cursor_name</replaceable> ]
*** a/doc/src/sgml/ref/insert.sgml
--- b/doc/src/sgml/ref/insert.sgml
***************
*** 21,26 **** PostgreSQL documentation
--- 21,27 ----
  
   <refsynopsisdiv>
  <synopsis>
+ [ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
  INSERT INTO <replaceable class="PARAMETER">table</replaceable> [ ( <replaceable class="PARAMETER">column</replaceable> [, ...] ) ]
      { DEFAULT VALUES | VALUES ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) [, ...] | <replaceable class="PARAMETER">query</replaceable> }
      [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
*** a/doc/src/sgml/ref/select.sgml
--- b/doc/src/sgml/ref/select.sgml
***************
*** 58,64 **** 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> )
  
  TABLE { [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] | <replaceable class="parameter">with_query_name</replaceable> }
  </synopsis>
--- 58,64 ----
  
  <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">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>
*** a/doc/src/sgml/ref/update.sgml
--- b/doc/src/sgml/ref/update.sgml
***************
*** 21,26 **** PostgreSQL documentation
--- 21,27 ----
  
   <refsynopsisdiv>
  <synopsis>
+ [ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
  UPDATE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
      SET { <replaceable class="PARAMETER">column</replaceable> = { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } |
            ( <replaceable class="PARAMETER">column</replaceable> [, ...] ) = ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) } [, ...]
*** a/src/backend/commands/portalcmds.c
--- b/src/backend/commands/portalcmds.c
***************
*** 48,53 **** PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
--- 48,58 ----
  	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");
  
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 935,941 **** ExecuteTruncate(TruncateStmt *stmt)
  		InitResultRelInfo(resultRelInfo,
  						  rel,
  						  0,	/* dummy rangetable index */
- 						  CMD_DELETE,	/* don't need any index info */
  						  0);
  		resultRelInfo++;
  	}
--- 935,940 ----
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
***************
*** 394,399 **** DefineView(ViewStmt *stmt, const char *queryString)
--- 394,400 ----
  	Query	   *viewParse;
  	Oid			viewOid;
  	RangeVar   *view;
+ 	ListCell   *lc;
  
  	/*
  	 * Run parse analysis to convert the raw parse tree to a Query.  Note this
***************
*** 412,417 **** DefineView(ViewStmt *stmt, const char *queryString)
--- 413,430 ----
  		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
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 84,89 **** static void intorel_destroy(DestReceiver *self);
--- 84,99 ----
  
  /* end of local decls */
  
+ static bool StatementIsReadOnly(PlannedStmt *stmt)
+ {
+ 	if (stmt->commandType == CMD_SELECT &&
+ 		stmt->intoClause == NULL &&
+ 		stmt->rowMarks == NULL)
+ 		return true;
+ 	else
+ 		return false;
+ }
+ 
  
  /* ----------------------------------------------------------------
   *		ExecutorStart
***************
*** 121,126 **** standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
--- 131,137 ----
  {
  	EState	   *estate;
  	MemoryContext oldcontext;
+ 	Snapshot snapshot;
  
  	/* sanity checks: queryDesc must not be started already */
  	Assert(queryDesc != NULL);
***************
*** 152,184 **** standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
  			palloc0(queryDesc->plannedstmt->nParamExec * sizeof(ParamExecData));
  
  	/*
! 	 * If non-read-only query, set the command ID to mark output tuples with
  	 */
! 	switch (queryDesc->operation)
  	{
! 		case CMD_SELECT:
! 			/* SELECT INTO and SELECT FOR UPDATE/SHARE need to mark tuples */
! 			if (queryDesc->plannedstmt->intoClause != NULL ||
! 				queryDesc->plannedstmt->rowMarks != NIL)
! 				estate->es_output_cid = GetCurrentCommandId(true);
! 			break;
! 
! 		case CMD_INSERT:
! 		case CMD_DELETE:
! 		case CMD_UPDATE:
! 			estate->es_output_cid = GetCurrentCommandId(true);
! 			break;
! 
! 		default:
! 			elog(ERROR, "unrecognized operation code: %d",
! 				 (int) queryDesc->operation);
! 			break;
  	}
  
! 	/*
! 	 * Copy other important information into the EState
! 	 */
! 	estate->es_snapshot = RegisterSnapshot(queryDesc->snapshot);
  	estate->es_crosscheck_snapshot = RegisterSnapshot(queryDesc->crosscheck_snapshot);
  	estate->es_instrument = queryDesc->instrument_options;
  
--- 163,185 ----
  			palloc0(queryDesc->plannedstmt->nParamExec * sizeof(ParamExecData));
  
  	/*
! 	 * If there are writeable CTEs, we always need to use the CID and
! 	 * copy the snapshot.  If there aren't any, we can safely use the
! 	 * snapshot provided to us and determine whether or not we use the
! 	 * CID with StatementIsReadOnly().
  	 */
! 	if (queryDesc->plannedstmt->hasWritableCtes)
  	{
! 		estate->es_output_cid = GetCurrentCommandId(true);
! 		snapshot = CopySnapshot(queryDesc->snapshot);
! 	}
! 	else
! 	{
! 		estate->es_output_cid = GetCurrentCommandId(!StatementIsReadOnly(queryDesc->plannedstmt));
! 		snapshot = queryDesc->snapshot;
  	}
  
! 	estate->es_snapshot = RegisterSnapshot(snapshot);
  	estate->es_crosscheck_snapshot = RegisterSnapshot(queryDesc->crosscheck_snapshot);
  	estate->es_instrument = queryDesc->instrument_options;
  
***************
*** 665,671 **** InitPlan(QueryDesc *queryDesc, int eflags)
  			InitResultRelInfo(resultRelInfo,
  							  resultRelation,
  							  resultRelationIndex,
- 							  operation,
  							  estate->es_instrument);
  			resultRelInfo++;
  		}
--- 666,671 ----
***************
*** 797,802 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 797,852 ----
  	planstate = ExecInitNode(plan, estate, eflags);
  
  	/*
+ 	 * Add writeable CTEs into estate.
+ 	 */
+ 	estate->prescans = NIL;
+ 
+ 	if (plannedstmt->hasWritableCtes)
+ 	{
+ 		foreach(l, plannedstmt->planTree->initPlan)
+ 		{
+ 			SubPlan *sp;
+ 			int cte_param_id;
+ 			ParamExecData *prmdata;
+ 			CteScanState *leader;
+ 			PlanState *ps;
+ 
+ 			sp = (SubPlan *) lfirst(l);
+ 			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 (leader)
+ 			{
+ 				ps = leader->cteplanstate;
+ 				
+ 				/* Ignore regular CTEs */
+ 				if (!IsA(ps, ModifyTableState))
+ 					continue;
+ 
+ 				/* Add the leader CTE, not the ModifyTable node. */
+ 				estate->prescans = lappend(estate->prescans, leader);
+ 			}
+ 			else
+ 			{
+ 				ps = (PlanState *) list_nth(estate->es_subplanstates,
+ 											sp->plan_id - 1);
+ 
+ 				/*
+ 				 * All non-referenced SELECT CTEs are removed from the plan
+ 				 * so this must be a DML query.
+ 				 */
+ 				Assert(IsA(ps, ModifyTableState));
+ 
+ 				estate->prescans = lappend(estate->prescans, ps);
+ 			}
+ 		}
+ 	}
+ 
+ 	/*
  	 * Get the tuple descriptor describing the type of tuples to return. (this
  	 * is especially important if we are creating a relation with "SELECT
  	 * INTO")
***************
*** 858,864 **** void
  InitResultRelInfo(ResultRelInfo *resultRelInfo,
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
- 				  CmdType operation,
  				  int instrument_options)
  {
  	/*
--- 908,913 ----
***************
*** 926,941 **** 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);
  }
  
  /*
--- 975,980 ----
***************
*** 991,1005 **** ExecGetTriggerResultRel(EState *estate, Oid relid)
  
  	/*
  	 * Make the new entry in the right context.  Currently, we don't need any
! 	 * index information in ResultRelInfos used only for triggers, so tell
! 	 * InitResultRelInfo it's a DELETE.
  	 */
  	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
  	rInfo = makeNode(ResultRelInfo);
  	InitResultRelInfo(rInfo,
  					  rel,
  					  0,		/* dummy rangetable index */
- 					  CMD_DELETE,
  					  estate->es_instrument);
  	estate->es_trig_target_relations =
  		lappend(estate->es_trig_target_relations, rInfo);
--- 1030,1042 ----
  
  	/*
  	 * Make the new entry in the right context.  Currently, we don't need any
! 	 * index information in ResultRelInfos used only for triggers.
  	 */
  	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
  	rInfo = makeNode(ResultRelInfo);
  	InitResultRelInfo(rInfo,
  					  rel,
  					  0,		/* dummy rangetable index */
  					  estate->es_instrument);
  	estate->es_trig_target_relations =
  		lappend(estate->es_trig_target_relations, rInfo);
***************
*** 1165,1170 **** ExecutePlan(EState *estate,
--- 1202,1208 ----
  {
  	TupleTableSlot *slot;
  	long		current_tuple_count;
+ 	ListCell	   *lc;
  
  	/*
  	 * initialize local variables
***************
*** 1176,1181 **** ExecutePlan(EState *estate,
--- 1214,1246 ----
  	 */
  	estate->es_direction = direction;
  
+ 	/* .. */
+ 	foreach(lc, estate->prescans)
+ 	{
+ 		TupleTableSlot *slot;
+ 		PlanState *ps = (PlanState *) lfirst(lc);
+ 
+ 		for (;;)
+ 		{
+ 			slot = ExecProcNode(ps);
+ 			if (TupIsNull(slot))
+ 				break;
+ 		}
+ 
+ 		/* Need to rewind the CTE */
+ 		if (!IsA(ps, ModifyTableState))
+ 			ExecReScan(ps, NULL);
+ 
+ 		CommandCounterIncrement();
+ 
+ 		/* If this was the last one, don't waste a CID unless necessary. */
+ 		if (!lnext(lc) && StatementIsReadOnly(estate->es_plannedstmt))
+ 			estate->es_output_cid = GetCurrentCommandId(false);
+ 		else
+ 			estate->es_output_cid = GetCurrentCommandId(true);
+ 		estate->es_snapshot->curcid = estate->es_output_cid;
+ 	}
+ 
  	/*
  	 * Loop until we've processed the proper number of tuples from the plan.
  	 */
***************
*** 1955,1961 **** 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.
  	 */
  	Assert(estate->es_subplanstates == NIL);
  	foreach(l, parentestate->es_plannedstmt->subplans)
--- 2020,2027 ----
  	 * 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.  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)
***************
*** 1963,1969 **** EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
  		Plan	   *subplan = (Plan *) lfirst(l);
  		PlanState  *subplanstate;
  
! 		subplanstate = ExecInitNode(subplan, estate, 0);
  
  		estate->es_subplanstates = lappend(estate->es_subplanstates,
  										   subplanstate);
--- 2029,2039 ----
  		Plan	   *subplan = (Plan *) lfirst(l);
  		PlanState  *subplanstate;
  
! 		/* 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);
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 589,603 **** fireBSTriggers(ModifyTableState *node)
  	{
  		case CMD_INSERT:
  			ExecBSInsertTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		case CMD_UPDATE:
  			ExecBSUpdateTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		case CMD_DELETE:
  			ExecBSDeleteTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		default:
  			elog(ERROR, "unknown operation");
--- 589,603 ----
  	{
  		case CMD_INSERT:
  			ExecBSInsertTriggers(node->ps.state,
! 								 node->resultRelInfo);
  			break;
  		case CMD_UPDATE:
  			ExecBSUpdateTriggers(node->ps.state,
! 								 node->resultRelInfo);
  			break;
  		case CMD_DELETE:
  			ExecBSDeleteTriggers(node->ps.state,
! 								 node->resultRelInfo);
  			break;
  		default:
  			elog(ERROR, "unknown operation");
***************
*** 615,629 **** fireASTriggers(ModifyTableState *node)
  	{
  		case CMD_INSERT:
  			ExecASInsertTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		case CMD_UPDATE:
  			ExecASUpdateTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		case CMD_DELETE:
  			ExecASDeleteTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		default:
  			elog(ERROR, "unknown operation");
--- 615,629 ----
  	{
  		case CMD_INSERT:
  			ExecASInsertTriggers(node->ps.state,
! 								 node->resultRelInfo);
  			break;
  		case CMD_UPDATE:
  			ExecASUpdateTriggers(node->ps.state,
! 								 node->resultRelInfo);
  			break;
  		case CMD_DELETE:
  			ExecASDeleteTriggers(node->ps.state,
! 								 node->resultRelInfo);
  			break;
  		default:
  			elog(ERROR, "unknown operation");
***************
*** 645,650 **** ExecModifyTable(ModifyTableState *node)
--- 645,651 ----
  	EState *estate = node->ps.state;
  	CmdType operation = node->operation;
  	PlanState *subplanstate;
+ 	ResultRelInfo *resultRelInfo;
  	JunkFilter *junkfilter;
  	TupleTableSlot *slot;
  	TupleTableSlot *planSlot;
***************
*** 660,676 **** 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;
  
  	/*
  	 * Fetch rows from subplan(s), and execute the required table modification
--- 661,670 ----
  		node->fireBSTriggers = false;
  	}
  
  	/* Preload local variables */
  	subplanstate = node->mt_plans[node->mt_whichplan];
! 	resultRelInfo = node->resultRelInfo + node->mt_whichplan;
! 	junkfilter = resultRelInfo->ri_junkFilter;
  
  	/*
  	 * Fetch rows from subplan(s), and execute the required table modification
***************
*** 686,694 **** 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;
  				EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan);
  				continue;
  			}
--- 680,688 ----
  			node->mt_whichplan++;
  			if (node->mt_whichplan < node->mt_nplans)
  			{
  				subplanstate = node->mt_plans[node->mt_whichplan];
! 				resultRelInfo = node->resultRelInfo + node->mt_whichplan;
! 				junkfilter = resultRelInfo->ri_junkFilter;
  				EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan);
  				continue;
  			}
***************
*** 726,731 **** ExecModifyTable(ModifyTableState *node)
--- 720,732 ----
  			if (operation != CMD_DELETE)
  				slot = ExecFilterJunk(junkfilter, slot);
  		}
+ 	
+ 		/*
+ 	 	 * es_result_relation_info must point to the currently active result
+ 	 	 * relation.  We want it to be NULL whenever we're not within
+ 	 	 * ModifyTable, though.
+ 		 */
+ 		estate->es_result_relation_info = resultRelInfo;
  
  		switch (operation)
  		{
***************
*** 805,829 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
  	mtstate->mt_nplans = nplans;
  	mtstate->operation = operation;
  	/* set up epqstate with dummy subplan pointer for the moment */
  	EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, node->epqParam);
  	mtstate->fireBSTriggers = true;
  
- 	/* For the moment, assume our targets are exactly the global result rels */
- 
  	/*
  	 * 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);
  		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
! 		estate->es_result_relation_info++;
  		i++;
  	}
  	estate->es_result_relation_info = NULL;
--- 806,844 ----
  	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;
  
  	/*
  	 * 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!
  	 */
  	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);
! 
! 		resultRelInfo++;
  		i++;
  	}
  	estate->es_result_relation_info = NULL;
***************
*** 860,867 **** 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;
  		foreach(l, node->returningLists)
  		{
  			List	   *rlist = (List *) lfirst(l);
--- 875,881 ----
  		/*
  		 * Build a projection for each result rel.
  		 */
! 		resultRelInfo = mtstate->resultRelInfo;
  		foreach(l, node->returningLists)
  		{
  			List	   *rlist = (List *) lfirst(l);
***************
*** 960,966 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  
  		if (junk_filter_needed)
  		{
! 			resultRelInfo = estate->es_result_relations;
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
--- 974,980 ----
  
  		if (junk_filter_needed)
  		{
! 			resultRelInfo = mtstate->resultRelInfo;
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
***************
*** 989,995 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  		else
  		{
  			if (operation == CMD_INSERT)
! 				ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc,
  									subplan->targetlist);
  		}
  	}
--- 1003,1009 ----
  		else
  		{
  			if (operation == CMD_INSERT)
! 				ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
  									subplan->targetlist);
  		}
  	}
*** a/src/backend/executor/nodeSubplan.c
--- b/src/backend/executor/nodeSubplan.c
***************
*** 668,673 **** ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
--- 668,675 ----
  	sstate->planstate = (PlanState *) list_nth(estate->es_subplanstates,
  											   subplan->plan_id - 1);
  
+ 	Assert(sstate->planstate != NULL);
+ 
  	/* Initialize subexpressions */
  	sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
  	sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent);
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 84,89 **** _copyPlannedStmt(PlannedStmt *from)
--- 84,90 ----
  	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,177 **** _copyModifyTable(ModifyTable *from)
--- 173,179 ----
  	 */
  	COPY_SCALAR_FIELD(operation);
  	COPY_NODE_FIELD(resultRelations);
+ 	COPY_SCALAR_FIELD(resultRelIndex);
  	COPY_NODE_FIELD(plans);
  	COPY_NODE_FIELD(returningLists);
  	COPY_NODE_FIELD(rowMarks);
***************
*** 2260,2265 **** _copyInsertStmt(InsertStmt *from)
--- 2262,2268 ----
  	COPY_NODE_FIELD(cols);
  	COPY_NODE_FIELD(selectStmt);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
***************
*** 2273,2278 **** _copyDeleteStmt(DeleteStmt *from)
--- 2276,2282 ----
  	COPY_NODE_FIELD(usingClause);
  	COPY_NODE_FIELD(whereClause);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
***************
*** 2287,2292 **** _copyUpdateStmt(UpdateStmt *from)
--- 2291,2297 ----
  	COPY_NODE_FIELD(whereClause);
  	COPY_NODE_FIELD(fromClause);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 888,893 **** _equalInsertStmt(InsertStmt *a, InsertStmt *b)
--- 888,894 ----
  	COMPARE_NODE_FIELD(cols);
  	COMPARE_NODE_FIELD(selectStmt);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
***************
*** 899,904 **** _equalDeleteStmt(DeleteStmt *a, DeleteStmt *b)
--- 900,906 ----
  	COMPARE_NODE_FIELD(usingClause);
  	COMPARE_NODE_FIELD(whereClause);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
***************
*** 911,916 **** _equalUpdateStmt(UpdateStmt *a, UpdateStmt *b)
--- 913,919 ----
  	COMPARE_NODE_FIELD(whereClause);
  	COMPARE_NODE_FIELD(fromClause);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 2394,2399 **** bool
--- 2394,2449 ----
  					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;
+ 				if (walker(stmt->withClause, 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;
+ 				if (walker(stmt->withClause, 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;
+ 				if (walker(stmt->withClause, context))
+ 					return true;
+ 			}
+ 			break;
  		case T_A_Expr:
  			{
  				A_Expr	   *expr = (A_Expr *) node;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 327,332 **** _outModifyTable(StringInfo str, ModifyTable *node)
--- 327,333 ----
  
  	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);
***************
*** 1533,1538 **** _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
--- 1534,1540 ----
  	WRITE_NODE_FIELD(finalrowmarks);
  	WRITE_NODE_FIELD(relationOids);
  	WRITE_NODE_FIELD(invalItems);
+ 	WRITE_NODE_FIELD(resultRelations);
  	WRITE_UINT_FIELD(lastPHId);
  	WRITE_BOOL_FIELD(transientPlan);
  }
***************
*** 1548,1554 **** _outPlannerInfo(StringInfo str, PlannerInfo *node)
  	WRITE_UINT_FIELD(query_level);
  	WRITE_NODE_FIELD(join_rel_list);
  	WRITE_INT_FIELD(join_cur_level);
- 	WRITE_NODE_FIELD(resultRelations);
  	WRITE_NODE_FIELD(init_plans);
  	WRITE_NODE_FIELD(cte_plan_ids);
  	WRITE_NODE_FIELD(eq_classes);
--- 1550,1555 ----
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 3763,3772 **** 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.
  	 */
--- 3763,3768 ----
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 160,165 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
--- 160,167 ----
  	glob->finalrowmarks = NIL;
  	glob->relationOids = NIL;
  	glob->invalItems = NIL;
+ 	glob->hasWritableCtes = false;
+ 	glob->resultRelations = NIL;
  	glob->lastPHId = 0;
  	glob->transientPlan = false;
  
***************
*** 237,245 **** 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->utilityStmt = parse->utilityStmt;
  	result->intoClause = parse->intoClause;
  	result->subplans = glob->subplans;
  	result->rewindPlanIDs = glob->rewindPlanIDs;
  	result->rowMarks = glob->finalrowmarks;
--- 239,248 ----
  	result->transientPlan = glob->transientPlan;
  	result->planTree = top_plan;
  	result->rtable = glob->finalrtable;
! 	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,547 **** subquery_planner(PlannerGlobal *glob, Query *parse,
  				rowMarks = root->rowMarks;
  
  			plan = (Plan *) make_modifytable(parse->commandType,
! 											 copyObject(root->resultRelations),
  											 list_make1(plan),
  											 returningLists,
  											 rowMarks,
--- 544,550 ----
  				rowMarks = root->rowMarks;
  
  			plan = (Plan *) make_modifytable(parse->commandType,
! 											 list_make1_int(parse->resultRelation),
  											 list_make1(plan),
  											 returningLists,
  											 rowMarks,
***************
*** 706,718 **** 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;
  
  	foreach(l, root->append_rel_list)
  	{
--- 709,721 ----
  	Query	   *parse = root->parse;
  	int			parentRTindex = parse->resultRelation;
  	List	   *subplans = 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,779 **** inheritance_planner(PlannerInfo *root)
  		}
  	}
  
- 	root->resultRelations = resultRelations;
- 
  	/* Mark result as unordered (probably unnecessary) */
  	root->query_pathkeys = NIL;
  
--- 775,780 ----
***************
*** 783,789 **** 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,
--- 784,789 ----
***************
*** 818,824 **** 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),
  									 subplans, 
  									 returningLists,
  									 rowMarks,
--- 818,824 ----
  
  	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
  	return (Plan *) make_modifytable(parse->commandType,
! 									 resultRelations,
  									 subplans, 
  									 returningLists,
  									 rowMarks,
***************
*** 1668,1679 **** 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.
--- 1668,1673 ----
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 520,525 **** set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
--- 520,529 ----
  											  (Plan *) lfirst(l),
  											  rtoffset);
  				}
+ 
+ 				splan->resultRelIndex = list_length(glob->resultRelations);
+ 				glob->resultRelations = list_concat(glob->resultRelations,
+ 													splan->resultRelations);
  			}
  			break;
  		case T_Append:
*** a/src/backend/optimizer/plan/subselect.c
--- b/src/backend/optimizer/plan/subselect.c
***************
*** 873,888 **** SS_process_ctes(PlannerInfo *root)
  		Bitmapset  *tmpset;
  		int			paramid;
  		Param	   *prm;
  
  		/*
! 		 * Ignore CTEs that are not actually referenced anywhere.
  		 */
! 		if (cte->cterefcount == 0)
  		{
  			/* Make a dummy entry in cte_plan_ids */
  			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
  			continue;
  		}
  
  		/*
  		 * Copy the source Query node.	Probably not necessary, but let's keep
--- 873,905 ----
  		Bitmapset  *tmpset;
  		int			paramid;
  		Param	   *prm;
+ 		CmdType		cmdType = ((Query *) cte->ctequery)->commandType;
  
  		/*
! 		 * Ignore SELECT CTEs that are not actually referenced anywhere.
  		 */
! 		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,904 **** SS_process_ctes(PlannerInfo *root)
--- 916,924 ----
  								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.
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 294,299 **** transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
--- 294,306 ----
  
  	qry->distinctClause = NIL;
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * The USING clause is non-standard SQL syntax, and is equivalent in
  	 * functionality to the FROM list that can be specified for UPDATE. The
***************
*** 346,351 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 353,365 ----
  	qry->commandType = CMD_INSERT;
  	pstate->p_is_insert = true;
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
  	 * VALUES list, or general SELECT input.  We special-case VALUES, both for
***************
*** 370,377 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
  		pstate->p_relnamespace = NIL;
  		sub_varnamespace = pstate->p_varnamespace;
  		pstate->p_varnamespace = NIL;
- 		/* There can't be any outer WITH to worry about */
- 		Assert(pstate->p_ctenamespace == NIL);
  	}
  	else
  	{
--- 384,389 ----
***************
*** 1739,1744 **** transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
--- 1751,1763 ----
  										 true,
  										 ACL_UPDATE);
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * the FROM clause is non-standard SQL syntax. We used to be able to do
  	 * this with REPLACE in POSTQUEL so we keep the feature.
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 428,434 **** static TypeName *TableFuncTypeName(List *columns);
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
! %type <with> 	with_clause
  %type <list>	cte_list
  
  %type <list>	window_clause window_definition_list opt_partition_clause
--- 428,434 ----
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
! %type <with> 	with_clause opt_with_clause
  %type <list>	cte_list
  
  %type <list>	window_clause window_definition_list opt_partition_clause
***************
*** 7014,7024 **** DeallocateStmt: DEALLOCATE name
   *****************************************************************************/
  
  InsertStmt:
! 			INSERT INTO qualified_name insert_rest returning_clause
  				{
! 					$4->relation = $3;
! 					$4->returningList = $5;
! 					$$ = (Node *) $4;
  				}
  		;
  
--- 7014,7025 ----
   *****************************************************************************/
  
  InsertStmt:
! 			opt_with_clause INSERT INTO qualified_name insert_rest returning_clause
  				{
! 					$5->relation = $4;
! 					$5->returningList = $6;
! 					$5->withClause = $1;
! 					$$ = (Node *) $5;
  				}
  		;
  
***************
*** 7074,7087 **** returning_clause:
   *
   *****************************************************************************/
  
! DeleteStmt: DELETE_P FROM relation_expr_opt_alias
  			using_clause where_or_current_clause returning_clause
  				{
  					DeleteStmt *n = makeNode(DeleteStmt);
! 					n->relation = $3;
! 					n->usingClause = $4;
! 					n->whereClause = $5;
! 					n->returningList = $6;
  					$$ = (Node *)n;
  				}
  		;
--- 7075,7089 ----
   *
   *****************************************************************************/
  
! DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias
  			using_clause where_or_current_clause returning_clause
  				{
  					DeleteStmt *n = makeNode(DeleteStmt);
! 					n->relation = $4;
! 					n->usingClause = $5;
! 					n->whereClause = $6;
! 					n->returningList = $7;
! 					n->withClause = $1;
  					$$ = (Node *)n;
  				}
  		;
***************
*** 7136,7153 **** opt_nowait:	NOWAIT							{ $$ = TRUE; }
   *
   *****************************************************************************/
  
! UpdateStmt: UPDATE relation_expr_opt_alias
  			SET set_clause_list
  			from_clause
  			where_or_current_clause
  			returning_clause
  				{
  					UpdateStmt *n = makeNode(UpdateStmt);
! 					n->relation = $2;
! 					n->targetList = $4;
! 					n->fromClause = $5;
! 					n->whereClause = $6;
! 					n->returningList = $7;
  					$$ = (Node *)n;
  				}
  		;
--- 7138,7156 ----
   *
   *****************************************************************************/
  
! UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
  			SET set_clause_list
  			from_clause
  			where_or_current_clause
  			returning_clause
  				{
  					UpdateStmt *n = makeNode(UpdateStmt);
! 					n->relation = $3;
! 					n->targetList = $5;
! 					n->fromClause = $6;
! 					n->whereClause = $7;
! 					n->returningList = $8;
! 					n->withClause = $1;
  					$$ = (Node *)n;
  				}
  		;
***************
*** 7473,7478 **** with_clause:
--- 7476,7485 ----
  			}
  		;
  
+ opt_with_clause:
+ 		with_clause								{ $$ = $1; }
+ 		| /*EMPTY*/								{ $$ = NULL; }
+ 
  cte_list:
  		common_table_expr						{ $$ = list_make1($1); }
  		| cte_list ',' common_table_expr		{ $$ = lappend($1, $3); }
***************
*** 7487,7492 **** common_table_expr:  name opt_name_list AS select_with_parens
--- 7494,7526 ----
  				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:
*** a/src/backend/parser/parse_cte.c
--- b/src/backend/parser/parse_cte.c
***************
*** 18,23 ****
--- 18,24 ----
  #include "nodes/nodeFuncs.h"
  #include "parser/analyze.h"
  #include "parser/parse_cte.h"
+ #include "nodes/plannodes.h"
  #include "utils/builtins.h"
  
  
***************
*** 225,246 **** static void
  analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
  {
  	Query	   *query;
! 
! 	/* Analysis not done already */
! 	Assert(IsA(cte->ctequery, SelectStmt));
  
  	query = parse_sub_analyze(cte->ctequery, pstate, cte, false);
  	cte->ctequery = (Node *) query;
  
  	/*
  	 * 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.)
  	 */
! 	if (!IsA(query, Query) ||
! 		query->commandType != CMD_SELECT ||
! 		query->utilityStmt != NULL)
! 		elog(ERROR, "unexpected non-SELECT command in subquery in WITH");
  	if (query->intoClause)
  		ereport(ERROR,
  				(errcode(ERRCODE_SYNTAX_ERROR),
--- 226,250 ----
  analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
  {
  	Query	   *query;
! 	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.
! 	 * 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.
  	 */
! 	Assert(IsA(query, Query) && query->utilityStmt == NULL);
! 
  	if (query->intoClause)
  		ereport(ERROR,
  				(errcode(ERRCODE_SYNTAX_ERROR),
***************
*** 251,257 **** analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
  	if (!cte->cterecursive)
  	{
  		/* Compute the output column names/types if not done yet */
! 		analyzeCTETargetList(pstate, cte, query->targetList);
  	}
  	else
  	{
--- 255,261 ----
  	if (!cte->cterecursive)
  	{
  		/* Compute the output column names/types if not done yet */
! 		analyzeCTETargetList(pstate, cte, cteList);
  	}
  	else
  	{
***************
*** 269,275 **** analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
  		lctyp = list_head(cte->ctecoltypes);
  		lctypmod = list_head(cte->ctecoltypmods);
  		varattno = 0;
! 		foreach(lctlist, query->targetList)
  		{
  			TargetEntry *te = (TargetEntry *) lfirst(lctlist);
  			Node	   *texpr;
--- 273,279 ----
  		lctyp = list_head(cte->ctecoltypes);
  		lctypmod = list_head(cte->ctecoltypmods);
  		varattno = 0;
! 		foreach(lctlist, cteList)
  		{
  			TargetEntry *te = (TargetEntry *) lfirst(lctlist);
  			Node	   *texpr;
*** a/src/backend/parser/parse_relation.c
--- b/src/backend/parser/parse_relation.c
***************
*** 24,29 ****
--- 24,30 ----
  #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"
*** a/src/backend/parser/parse_target.c
--- b/src/backend/parser/parse_target.c
***************
*** 314,323 **** markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 				ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
  									   attnum);
  				if (ste == NULL || ste->resjunk)
  					elog(ERROR, "subquery %s does not have attribute %d",
--- 314,333 ----
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
+ 				List		*cteList;
+ 				Query		*ctequery;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 
! 				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,1355 **** expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 				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);
--- 1355,1374 ----
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
+ 				List		*cteList;
+ 				Query		*ctequery;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 
! 				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,1378 **** expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
  						 levelsup++)
  						pstate = pstate->parentParseState;
  					mypstate.parentParseState = pstate;
! 					mypstate.p_rtable = ((Query *) cte->ctequery)->rtable;
  					/* don't bother filling the rest of the fake pstate */
  
  					return expandRecordVariable(&mypstate, (Var *) expr, 0);
--- 1391,1397 ----
  						 levelsup++)
  						pstate = pstate->parentParseState;
  					mypstate.parentParseState = pstate;
! 					mypstate.p_rtable = ctequery->rtable;
  					/* don't bother filling the rest of the fake pstate */
  
  					return expandRecordVariable(&mypstate, (Var *) expr, 0);
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 1632,1637 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1632,1641 ----
  	bool		returning = false;
  	Query	   *qual_product = NULL;
  	List	   *rewritten = NIL;
+ 	ListCell	*lc1;
+ 	CommonTableExpr	*cte;
+ 	Query		*ctequery;
+ 	List		*newstuff;
  
  	/*
  	 * If the statement is an update, insert or delete - fire rules on it.
***************
*** 1749,1755 **** 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);
--- 1753,1758 ----
***************
*** 1804,1809 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1807,1866 ----
  	}
  
  	/*
+ 	 * Rewrite DML statements inside CTEs.
+ 	 */
+ 	foreach(lc1, parsetree->cteList)
+ 	{
+ 		cte = lfirst(lc1);
+ 
+ 		ctequery = (Query *) cte->ctequery;
+ 
+ 		if (ctequery->commandType == CMD_SELECT)
+ 			continue;
+ 
+ 		newstuff = RewriteQuery(ctequery, NIL);
+ 
+ 		/* Currently we can only handle unconditional DO INSTEAD rules correctly. */
+ 		if (list_length(newstuff) > 1)
+ 		{
+ 			ListCell *lc2;
+ 
+ 			foreach(lc2, newstuff)
+ 			{
+ 				QuerySource qsrc = ((Query *) lfirst(lc2))->querySource;
+ 				
+ 				if (qsrc == QSRC_QUAL_INSTEAD_RULE)
+ 				{
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("Conditional DO INSTEAD rules aren't supported in DML WITH statements")));
+ 				}
+ 				else if (qsrc == QSRC_NON_INSTEAD_RULE)
+ 				{
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("DO ALSO rules aren't supported in DML WITH statements")));
+ 				}
+ 			}
+ 
+ 			/* we should never get here */
+ 			elog(ERROR, "unknown rewrite result");
+ 		}
+ 		else if (list_length(newstuff) == 0)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 					 errmsg("DO INSTEAD NOTHING rules aren't supported in DML WITH statements")));
+ 		}
+ 		else
+ 		{
+ 			Assert(list_length(newstuff) == 1);
+ 
+ 			cte->ctequery = (Node *) linitial(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
*** a/src/backend/tcop/pquery.c
--- b/src/backend/tcop/pquery.c
***************
*** 293,298 **** ChoosePortalStrategy(List *stmts)
--- 293,299 ----
  			if (pstmt->canSetTag)
  			{
  				if (pstmt->commandType == CMD_SELECT &&
+ 					pstmt->hasWritableCtes == false &&
  					pstmt->utilityStmt == NULL &&
  					pstmt->intoClause == NULL)
  					return PORTAL_ONE_SELECT;
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 3978,3986 **** 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);
  
  					if (ste == NULL || ste->resjunk)
  						elog(ERROR, "subquery %s does not have attribute %d",
--- 3978,3993 ----
  				}
  				if (lc != NULL)
  				{
! 					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",
*** a/src/backend/utils/time/snapmgr.c
--- b/src/backend/utils/time/snapmgr.c
***************
*** 104,110 **** bool		FirstSnapshotSet = false;
  static bool registered_serializable = false;
  
  
- static Snapshot CopySnapshot(Snapshot snapshot);
  static void FreeSnapshot(Snapshot snapshot);
  static void SnapshotResetXmin(void);
  
--- 104,109 ----
***************
*** 192,198 **** SnapshotSetCommandId(CommandId curcid)
   * The copy is palloc'd in TopTransactionContext and has initial refcounts set
   * to 0.  The returned snapshot has the copied flag set.
   */
! static Snapshot
  CopySnapshot(Snapshot snapshot)
  {
  	Snapshot	newsnap;
--- 191,197 ----
   * The copy is palloc'd in TopTransactionContext and has initial refcounts set
   * to 0.  The returned snapshot has the copied flag set.
   */
! Snapshot
  CopySnapshot(Snapshot snapshot)
  {
  	Snapshot	newsnap;
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 160,166 **** extern void ExecutorRewind(QueryDesc *queryDesc);
  extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
- 				  CmdType operation,
  				  int instrument_options);
  extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
  extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
--- 160,165 ----
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 376,381 **** typedef struct EState
--- 376,382 ----
  
  	List	   *es_exprcontexts;	/* List of ExprContexts within EState */
  
+ 	List	   *prescans;
  	List	   *es_subplanstates;		/* List of PlanState for SubPlans */
  
  	/*
***************
*** 1029,1034 **** typedef struct ModifyTableState
--- 1030,1037 ----
  	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;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 879,884 **** typedef struct InsertStmt
--- 879,885 ----
  	List	   *cols;			/* optional: names of the target columns */
  	Node	   *selectStmt;		/* the source SELECT/VALUES, or NULL */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } InsertStmt;
  
  /* ----------------------
***************
*** 892,897 **** typedef struct DeleteStmt
--- 893,899 ----
  	List	   *usingClause;	/* optional using clause for more tables */
  	Node	   *whereClause;	/* qualifications */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } DeleteStmt;
  
  /* ----------------------
***************
*** 906,911 **** typedef struct UpdateStmt
--- 908,914 ----
  	Node	   *whereClause;	/* qualifications */
  	List	   *fromClause;		/* optional from clause for more tables */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } UpdateStmt;
  
  /* ----------------------
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 55,60 **** typedef struct PlannedStmt
--- 55,62 ----
  
  	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,170 **** typedef struct ModifyTable
--- 167,173 ----
  	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) */
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
***************
*** 80,85 **** typedef struct PlannerGlobal
--- 80,89 ----
  
  	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? */
***************
*** 152,159 **** typedef struct PlannerInfo
  	List	  **join_rel_level;	/* lists of join-relation RelOptInfos */
  	int			join_cur_level;	/* index of list being extended */
  
- 	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 */
--- 156,161 ----
*** a/src/include/utils/snapmgr.h
--- b/src/include/utils/snapmgr.h
***************
*** 16,21 ****
--- 16,22 ----
  #include "utils/resowner.h"
  #include "utils/snapshot.h"
  
+ extern Snapshot CopySnapshot(Snapshot snapshot);
  
  extern bool FirstSnapshotSet;
  
*** a/src/test/regress/expected/with.out
--- b/src/test/regress/expected/with.out
***************
*** 1026,1028 **** SELECT * FROM t;
--- 1026,1140 ----
   10
  (55 rows)
  
+ --
+ -- Writeable CTEs
+ --
+ 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)
+ 
+ WITH t AS (
+ 	UPDATE y SET a = a-11
+ ), t2 AS (
+ 	DELETE FROM y WHERE a <= 5
+ )
+ UPDATE y SET a=a+1 RETURNING *;
+  a  
+ ----
+   7
+   8
+   9
+  10
+  11
+ (5 rows)
+ 
+ WITH t AS (
+ 	UPDATE y SET a=a-100
+ ), t2 AS (
+ 	UPDATE y SET a=a+94
+ )
+ SELECT * FROM y;
+  a 
+ ---
+  1
+  2
+  3
+  4
+  5
+ (5 rows)
+ 
*** a/src/test/regress/sql/with.sql
--- b/src/test/regress/sql/with.sql
***************
*** 500,502 **** WITH RECURSIVE t(j) AS (
--- 500,551 ----
      SELECT j+1 FROM t WHERE j < 10
  )
  SELECT * FROM t;
+ 
+ --
+ -- Writeable CTEs
+ --
+ 
+ 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;
+ 
+ WITH t AS (
+ 	UPDATE y SET a = a-11
+ ), t2 AS (
+ 	DELETE FROM y WHERE a <= 5
+ )
+ UPDATE y SET a=a+1 RETURNING *;
+ 
+ WITH t AS (
+ 	UPDATE y SET a=a-100
+ ), t2 AS (
+ 	UPDATE y SET a=a+94
+ )
+ SELECT * FROM y;
#2Greg Stark
gsstark@mit.edu
In reply to: Marko Tiikkaja (#1)
Re: Writeable CTEs

On Tue, Jan 5, 2010 at 4:42 PM, Marko Tiikkaja
<marko.tiikkaja@cs.helsinki.fi> wrote:

=> with t as (delete from foo returning *)
-> insert into bar
-> select * from t;
INSERT 0 2

It correctly reports 2 affected rows (one deleted and one inserted), but is
this the answer we want?  It doesn't seem all that useful to know the total
amount of affected rows.

My first thought is that the number should correspond to the INSERT.
It didn't INSERT two rows so it seems wrong. More importantly in a
case like

with t as (delete from foo returning *)
select * from t where x=?

applications will almost certainly expect the number to match the
actual number of rows returned and may well misbehave if they don't.

--
greg

#3David Fetter
david@fetter.org
In reply to: Greg Stark (#2)
Re: Writeable CTEs

On Tue, Jan 05, 2010 at 05:21:12PM +0000, Greg Stark wrote:

On Tue, Jan 5, 2010 at 4:42 PM, Marko Tiikkaja
<marko.tiikkaja@cs.helsinki.fi> wrote:

=> with t as (delete from foo returning *)
-> insert into bar
-> select * from t;
INSERT 0 2

It correctly reports 2 affected rows (one deleted and one
inserted), but is this the answer we want? �It doesn't seem all
that useful to know the total amount of affected rows.

My first thought is that the number should correspond to the INSERT.
It didn't INSERT two rows so it seems wrong. More importantly in a
case like

with t as (delete from foo returning *) select * from t where x=?

applications will almost certainly expect the number to match the
actual number of rows returned and may well misbehave if they don't.

I'm not sure how relevant this could be, as existing apps can't use
future functionality. We have precedents with RULEs, which can make
the arguments pretty meaningless.

In some future version, we may want to redo the infrastructure to
support "modified" values for multiple statements, but for now, that
seems like an unnecessary frill.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#4Marko Tiikkaja
marko.tiikkaja@cs.helsinki.fi
In reply to: Greg Stark (#2)
Re: Writeable CTEs

On 2010-01-05 19:21 +0200, Greg Stark wrote:

with t as (delete from foo returning *)
select * from t where x=?

applications will almost certainly expect the number to match the
actual number of rows returned and may well misbehave if they don't.

I probably wasn't clear about the actual problem in the original post.
The problem only affects INSERT, UDPATE and DELETE where you are
actually counting affected rows (i.e. PQcmdTuples(), not PQntuples()) so
the this example would work as expected.

Regards,
Marko Tiikkaja

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Marko Tiikkaja (#1)
Re: Writeable CTEs

Marko Tiikkaja <marko.tiikkaja@cs.helsinki.fi> writes:

=> with t as (delete from foo returning *)
-> insert into bar
-> select * from t;
INSERT 0 2

It correctly reports 2 affected rows (one deleted and one inserted), but
is this the answer we want?

No. The returned tag should consider only the top-level operation,
not what happened inside any CTEs.

regards, tom lane

#6Josh Berkus
josh@agliodbs.com
In reply to: Marko Tiikkaja (#4)
Re: Writeable CTEs

On 1/5/10 9:45 AM, Marko Tiikkaja wrote:

On 2010-01-05 19:21 +0200, Greg Stark wrote:

with t as (delete from foo returning *)
select * from t where x=?

applications will almost certainly expect the number to match the
actual number of rows returned and may well misbehave if they don't.

I probably wasn't clear about the actual problem in the original post.
The problem only affects INSERT, UDPATE and DELETE where you are
actually counting affected rows (i.e. PQcmdTuples(), not PQntuples()) so
the this example would work as expected.

I don't think there is an "as expected" for this situation; people won't
know what to expect. So what do we think is resonable? The current
behavior, which reports the total count of rows expected, works for me.

--Josh Berkus

#7Robert Haas
robertmhaas@gmail.com
In reply to: Josh Berkus (#6)
Re: Writeable CTEs

On Tue, Jan 5, 2010 at 4:20 PM, Josh Berkus <josh@agliodbs.com> wrote:

On 1/5/10 9:45 AM, Marko Tiikkaja wrote:

On 2010-01-05 19:21 +0200, Greg Stark wrote:

with t as (delete from foo returning *)
select * from t where x=?

applications will almost certainly expect the number to match the
actual number of rows returned and may well misbehave if they don't.

I probably wasn't clear about the actual problem in the original post.
The problem only affects INSERT, UDPATE and DELETE where you are
actually counting affected rows (i.e. PQcmdTuples(), not PQntuples()) so
the this example would work as expected.

I don't think there is an "as expected" for this situation; people won't
know what to expect. So what do we think is resonable?  The current
behavior, which reports the total count of rows expected, works for me.

I agree with Tom's statement upthread that we should only count the
rows affected by the top-level query. Anything else seems extremely
counter-intuitive.

...Robert

#8Josh Berkus
josh@agliodbs.com
In reply to: Robert Haas (#7)
Re: Writeable CTEs

I agree with Tom's statement upthread that we should only count the
rows affected by the top-level query. Anything else seems extremely
counter-intuitive.

I'm ok with that. I don't think there is any kind of intuitive behavior
in this situation, and we just need to pick something and document it.

--Josh

#9Marko Tiikkaja
marko.tiikkaja@cs.helsinki.fi
In reply to: Tom Lane (#5)
1 attachment(s)
Re: Writeable CTEs

On 2010-01-05 20:40 +0200, Tom Lane wrote:

Marko Tiikkaja <marko.tiikkaja@cs.helsinki.fi> writes:

=> with t as (delete from foo returning *)
-> insert into bar
-> select * from t;
INSERT 0 2

It correctly reports 2 affected rows (one deleted and one inserted), but
is this the answer we want?

No. The returned tag should consider only the top-level operation,
not what happened inside any CTEs.

Attached is the latest version of the patch. This fixes all issues I'm
aware of. This one also allows forward-referencing and recursive CTEs
in the same CTE list with writeable CTEs, using the RECURSIVE keyword.

Regards,
Marko Tiikkaja

Attachments:

writeable5.patchtext/plain; charset=iso-8859-1; name=writeable5.patchDownload
*** a/doc/src/sgml/queries.sgml
--- b/doc/src/sgml/queries.sgml
***************
*** 1529,1538 **** 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:
  
  <programlisting>
  WITH regional_sales AS (
--- 1529,1539 ----
    </indexterm>
  
    <para>
!    <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,1565 **** GROUP BY region, product;
--- 1561,1588 ----
    </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 *
+      )
+ INSERT INTO foo_log_200910
+ SELECT * FROM rows
+ </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
*** a/doc/src/sgml/ref/delete.sgml
--- b/doc/src/sgml/ref/delete.sgml
***************
*** 21,26 **** PostgreSQL documentation
--- 21,27 ----
  
   <refsynopsisdiv>
  <synopsis>
+ [ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
  DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
      [ USING <replaceable class="PARAMETER">using_list</replaceable> ]
      [ WHERE <replaceable class="PARAMETER">condition</replaceable> | WHERE CURRENT OF <replaceable class="PARAMETER">cursor_name</replaceable> ]
*** a/doc/src/sgml/ref/insert.sgml
--- b/doc/src/sgml/ref/insert.sgml
***************
*** 21,26 **** PostgreSQL documentation
--- 21,27 ----
  
   <refsynopsisdiv>
  <synopsis>
+ [ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
  INSERT INTO <replaceable class="PARAMETER">table</replaceable> [ ( <replaceable class="PARAMETER">column</replaceable> [, ...] ) ]
      { DEFAULT VALUES | VALUES ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) [, ...] | <replaceable class="PARAMETER">query</replaceable> }
      [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
*** a/doc/src/sgml/ref/select.sgml
--- b/doc/src/sgml/ref/select.sgml
***************
*** 58,64 **** 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> )
  
  TABLE { [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] | <replaceable class="parameter">with_query_name</replaceable> }
  </synopsis>
--- 58,64 ----
  
  <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">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>
*** a/doc/src/sgml/ref/update.sgml
--- b/doc/src/sgml/ref/update.sgml
***************
*** 21,26 **** PostgreSQL documentation
--- 21,27 ----
  
   <refsynopsisdiv>
  <synopsis>
+ [ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
  UPDATE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
      SET { <replaceable class="PARAMETER">column</replaceable> = { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } |
            ( <replaceable class="PARAMETER">column</replaceable> [, ...] ) = ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) } [, ...]
*** a/src/backend/commands/portalcmds.c
--- b/src/backend/commands/portalcmds.c
***************
*** 48,53 **** PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
--- 48,58 ----
  	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");
  
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 935,941 **** ExecuteTruncate(TruncateStmt *stmt)
  		InitResultRelInfo(resultRelInfo,
  						  rel,
  						  0,	/* dummy rangetable index */
- 						  CMD_DELETE,	/* don't need any index info */
  						  0);
  		resultRelInfo++;
  	}
--- 935,940 ----
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
***************
*** 394,399 **** DefineView(ViewStmt *stmt, const char *queryString)
--- 394,400 ----
  	Query	   *viewParse;
  	Oid			viewOid;
  	RangeVar   *view;
+ 	ListCell   *lc;
  
  	/*
  	 * Run parse analysis to convert the raw parse tree to a Query.  Note this
***************
*** 412,417 **** DefineView(ViewStmt *stmt, const char *queryString)
--- 413,430 ----
  		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
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 72,77 **** static void ExecutePlan(EState *estate, PlanState *planstate,
--- 72,78 ----
  			DestReceiver *dest);
  static void ExecCheckRTPerms(List *rangeTable);
  static void ExecCheckRTEPerms(RangeTblEntry *rte);
+ static bool ExecTopLevelStmtIsReadOnly(PlannedStmt *stmt);
  static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
  static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
  							  Plan *planTree);
***************
*** 121,126 **** standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
--- 122,128 ----
  {
  	EState	   *estate;
  	MemoryContext oldcontext;
+ 	Snapshot snapshot;
  
  	/* sanity checks: queryDesc must not be started already */
  	Assert(queryDesc != NULL);
***************
*** 152,184 **** standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
  			palloc0(queryDesc->plannedstmt->nParamExec * sizeof(ParamExecData));
  
  	/*
! 	 * If non-read-only query, set the command ID to mark output tuples with
  	 */
! 	switch (queryDesc->operation)
  	{
! 		case CMD_SELECT:
! 			/* SELECT INTO and SELECT FOR UPDATE/SHARE need to mark tuples */
! 			if (queryDesc->plannedstmt->intoClause != NULL ||
! 				queryDesc->plannedstmt->rowMarks != NIL)
! 				estate->es_output_cid = GetCurrentCommandId(true);
! 			break;
! 
! 		case CMD_INSERT:
! 		case CMD_DELETE:
! 		case CMD_UPDATE:
! 			estate->es_output_cid = GetCurrentCommandId(true);
! 			break;
! 
! 		default:
! 			elog(ERROR, "unrecognized operation code: %d",
! 				 (int) queryDesc->operation);
! 			break;
  	}
  
! 	/*
! 	 * Copy other important information into the EState
! 	 */
! 	estate->es_snapshot = RegisterSnapshot(queryDesc->snapshot);
  	estate->es_crosscheck_snapshot = RegisterSnapshot(queryDesc->crosscheck_snapshot);
  	estate->es_instrument = queryDesc->instrument_options;
  
--- 154,176 ----
  			palloc0(queryDesc->plannedstmt->nParamExec * sizeof(ParamExecData));
  
  	/*
! 	 * If there are writeable CTEs, we always need to use the CID and
! 	 * copy the snapshot.  If there aren't any, we can safely use the
! 	 * snapshot provided to us and determine whether or not we use the
! 	 * CID with ExecTopLevelStmtIsReadOnly().
  	 */
! 	if (queryDesc->plannedstmt->hasWritableCtes)
  	{
! 		estate->es_output_cid = GetCurrentCommandId(true);
! 		snapshot = CopySnapshot(queryDesc->snapshot);
! 	}
! 	else
! 	{
! 		estate->es_output_cid = GetCurrentCommandId(!ExecTopLevelStmtIsReadOnly(queryDesc->plannedstmt));
! 		snapshot = queryDesc->snapshot;
  	}
  
! 	estate->es_snapshot = RegisterSnapshot(snapshot);
  	estate->es_crosscheck_snapshot = RegisterSnapshot(queryDesc->crosscheck_snapshot);
  	estate->es_instrument = queryDesc->instrument_options;
  
***************
*** 566,571 **** ExecCheckRTEPerms(RangeTblEntry *rte)
--- 558,580 ----
  }
  
  /*
+  * Is the top level statement read-only?
+  *
+  * Ehm.  This is more like "would this statement be read-only
+  * if it didn't have writeable CTEs in it?"
+  */
+ static bool ExecTopLevelStmtIsReadOnly(PlannedStmt *stmt)
+ {
+ 	if (stmt->commandType == CMD_SELECT &&
+ 		stmt->intoClause == NULL &&
+ 		stmt->rowMarks == NULL)
+ 		return true;
+ 	else
+ 		return false;
+ }
+ 
+ 
+ /*
   * Check that the query does not imply any writes to non-temp tables.
   */
  static void
***************
*** 665,671 **** InitPlan(QueryDesc *queryDesc, int eflags)
  			InitResultRelInfo(resultRelInfo,
  							  resultRelation,
  							  resultRelationIndex,
- 							  operation,
  							  estate->es_instrument);
  			resultRelInfo++;
  		}
--- 674,679 ----
***************
*** 797,802 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 805,860 ----
  	planstate = ExecInitNode(plan, estate, eflags);
  
  	/*
+ 	 * Add writeable CTEs into estate.
+ 	 */
+ 	estate->prescans = NIL;
+ 
+ 	if (plannedstmt->hasWritableCtes)
+ 	{
+ 		foreach(l, plannedstmt->planTree->initPlan)
+ 		{
+ 			SubPlan *sp;
+ 			int cte_param_id;
+ 			ParamExecData *prmdata;
+ 			CteScanState *leader;
+ 			PlanState *ps;
+ 
+ 			sp = (SubPlan *) lfirst(l);
+ 			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 (leader)
+ 			{
+ 				ps = leader->cteplanstate;
+ 				
+ 				/* Ignore regular CTEs */
+ 				if (!IsA(ps, ModifyTableState))
+ 					continue;
+ 
+ 				/* Add the leader CTE, not the ModifyTable node. */
+ 				estate->prescans = lappend(estate->prescans, leader);
+ 			}
+ 			else
+ 			{
+ 				ps = (PlanState *) list_nth(estate->es_subplanstates,
+ 											sp->plan_id - 1);
+ 
+ 				/*
+ 				 * All non-referenced SELECT CTEs are removed from the plan
+ 				 * so this must be a DML query.
+ 				 */
+ 				Assert(IsA(ps, ModifyTableState));
+ 
+ 				estate->prescans = lappend(estate->prescans, ps);
+ 			}
+ 		}
+ 	}
+ 
+ 	/*
  	 * Get the tuple descriptor describing the type of tuples to return. (this
  	 * is especially important if we are creating a relation with "SELECT
  	 * INTO")
***************
*** 858,864 **** void
  InitResultRelInfo(ResultRelInfo *resultRelInfo,
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
- 				  CmdType operation,
  				  int instrument_options)
  {
  	/*
--- 916,921 ----
***************
*** 926,941 **** 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);
  }
  
  /*
--- 983,988 ----
***************
*** 991,1005 **** ExecGetTriggerResultRel(EState *estate, Oid relid)
  
  	/*
  	 * Make the new entry in the right context.  Currently, we don't need any
! 	 * index information in ResultRelInfos used only for triggers, so tell
! 	 * InitResultRelInfo it's a DELETE.
  	 */
  	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
  	rInfo = makeNode(ResultRelInfo);
  	InitResultRelInfo(rInfo,
  					  rel,
  					  0,		/* dummy rangetable index */
- 					  CMD_DELETE,
  					  estate->es_instrument);
  	estate->es_trig_target_relations =
  		lappend(estate->es_trig_target_relations, rInfo);
--- 1038,1050 ----
  
  	/*
  	 * Make the new entry in the right context.  Currently, we don't need any
! 	 * index information in ResultRelInfos used only for triggers.
  	 */
  	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
  	rInfo = makeNode(ResultRelInfo);
  	InitResultRelInfo(rInfo,
  					  rel,
  					  0,		/* dummy rangetable index */
  					  estate->es_instrument);
  	estate->es_trig_target_relations =
  		lappend(estate->es_trig_target_relations, rInfo);
***************
*** 1165,1170 **** ExecutePlan(EState *estate,
--- 1210,1216 ----
  {
  	TupleTableSlot *slot;
  	long		current_tuple_count;
+ 	ListCell	   *lc;
  
  	/*
  	 * initialize local variables
***************
*** 1176,1181 **** ExecutePlan(EState *estate,
--- 1222,1254 ----
  	 */
  	estate->es_direction = direction;
  
+ 	/* .. */
+ 	foreach(lc, estate->prescans)
+ 	{
+ 		TupleTableSlot *slot;
+ 		PlanState *ps = (PlanState *) lfirst(lc);
+ 
+ 		for (;;)
+ 		{
+ 			slot = ExecProcNode(ps);
+ 			if (TupIsNull(slot))
+ 				break;
+ 		}
+ 
+ 		/* Need to rewind the CTE */
+ 		if (!IsA(ps, ModifyTableState))
+ 			ExecReScan(ps, NULL);
+ 
+ 		CommandCounterIncrement();
+ 
+ 		/* If this was the last one, don't waste a CID unless necessary. */
+ 		if (!lnext(lc) && ExecTopLevelStmtIsReadOnly(estate->es_plannedstmt))
+ 			estate->es_output_cid = GetCurrentCommandId(false);
+ 		else
+ 			estate->es_output_cid = GetCurrentCommandId(true);
+ 		estate->es_snapshot->curcid = estate->es_output_cid;
+ 	}
+ 
  	/*
  	 * Loop until we've processed the proper number of tuples from the plan.
  	 */
***************
*** 1957,1963 **** 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.
  	 */
  	Assert(estate->es_subplanstates == NIL);
  	foreach(l, parentestate->es_plannedstmt->subplans)
--- 2030,2037 ----
  	 * 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.  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)
***************
*** 1965,1971 **** EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
  		Plan	   *subplan = (Plan *) lfirst(l);
  		PlanState  *subplanstate;
  
! 		subplanstate = ExecInitNode(subplan, estate, 0);
  
  		estate->es_subplanstates = lappend(estate->es_subplanstates,
  										   subplanstate);
--- 2039,2049 ----
  		Plan	   *subplan = (Plan *) lfirst(l);
  		PlanState  *subplanstate;
  
! 		/* 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);
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 158,164 **** ExecProcessReturning(ProjectionInfo *projectReturning,
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecInsert(TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EState *estate)
  {
--- 158,165 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecInsert(bool canSetTag,
! 		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EState *estate)
  {
***************
*** 240,246 **** ExecInsert(TupleTableSlot *slot,
  	newId = heap_insert(resultRelationDesc, tuple,
  						estate->es_output_cid, 0, NULL);
  
! 	(estate->es_processed)++;
  	estate->es_lastoid = newId;
  	setLastTid(&(tuple->t_self));
  
--- 241,249 ----
  	newId = heap_insert(resultRelationDesc, tuple,
  						estate->es_output_cid, 0, NULL);
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
! 
  	estate->es_lastoid = newId;
  	setLastTid(&(tuple->t_self));
  
***************
*** 272,278 **** ExecInsert(TupleTableSlot *slot,
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(ItemPointer tupleid,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
--- 275,282 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(bool canSetTag,
! 		   ItemPointer tupleid,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
***************
*** 354,360 **** ldelete:;
  			return NULL;
  	}
  
! 	(estate->es_processed)++;
  
  	/*
  	 * Note: Normally one would think that we have to delete index tuples
--- 358,365 ----
  			return NULL;
  	}
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
  
  	/*
  	 * Note: Normally one would think that we have to delete index tuples
***************
*** 415,421 **** ldelete:;
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(ItemPointer tupleid,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
--- 420,427 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(bool canSetTag,
! 		   ItemPointer tupleid,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
***************
*** 544,550 **** lreplace:;
  			return NULL;
  	}
  
! 	(estate->es_processed)++;
  
  	/*
  	 * Note: instead of having to update the old index tuples associated with
--- 550,557 ----
  			return NULL;
  	}
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
  
  	/*
  	 * Note: instead of having to update the old index tuples associated with
***************
*** 589,603 **** fireBSTriggers(ModifyTableState *node)
  	{
  		case CMD_INSERT:
  			ExecBSInsertTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		case CMD_UPDATE:
  			ExecBSUpdateTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		case CMD_DELETE:
  			ExecBSDeleteTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		default:
  			elog(ERROR, "unknown operation");
--- 596,610 ----
  	{
  		case CMD_INSERT:
  			ExecBSInsertTriggers(node->ps.state,
! 								 node->resultRelInfo);
  			break;
  		case CMD_UPDATE:
  			ExecBSUpdateTriggers(node->ps.state,
! 								 node->resultRelInfo);
  			break;
  		case CMD_DELETE:
  			ExecBSDeleteTriggers(node->ps.state,
! 								 node->resultRelInfo);
  			break;
  		default:
  			elog(ERROR, "unknown operation");
***************
*** 615,629 **** fireASTriggers(ModifyTableState *node)
  	{
  		case CMD_INSERT:
  			ExecASInsertTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		case CMD_UPDATE:
  			ExecASUpdateTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		case CMD_DELETE:
  			ExecASDeleteTriggers(node->ps.state,
! 								 node->ps.state->es_result_relations);
  			break;
  		default:
  			elog(ERROR, "unknown operation");
--- 622,636 ----
  	{
  		case CMD_INSERT:
  			ExecASInsertTriggers(node->ps.state,
! 								 node->resultRelInfo);
  			break;
  		case CMD_UPDATE:
  			ExecASUpdateTriggers(node->ps.state,
! 								 node->resultRelInfo);
  			break;
  		case CMD_DELETE:
  			ExecASDeleteTriggers(node->ps.state,
! 								 node->resultRelInfo);
  			break;
  		default:
  			elog(ERROR, "unknown operation");
***************
*** 645,650 **** ExecModifyTable(ModifyTableState *node)
--- 652,658 ----
  	EState *estate = node->ps.state;
  	CmdType operation = node->operation;
  	PlanState *subplanstate;
+ 	ResultRelInfo *resultRelInfo;
  	JunkFilter *junkfilter;
  	TupleTableSlot *slot;
  	TupleTableSlot *planSlot;
***************
*** 660,676 **** 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;
  
  	/*
  	 * Fetch rows from subplan(s), and execute the required table modification
--- 668,677 ----
  		node->fireBSTriggers = false;
  	}
  
  	/* Preload local variables */
  	subplanstate = node->mt_plans[node->mt_whichplan];
! 	resultRelInfo = node->resultRelInfo + node->mt_whichplan;
! 	junkfilter = resultRelInfo->ri_junkFilter;
  
  	/*
  	 * Fetch rows from subplan(s), and execute the required table modification
***************
*** 686,694 **** 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;
  				EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan);
  				continue;
  			}
--- 687,695 ----
  			node->mt_whichplan++;
  			if (node->mt_whichplan < node->mt_nplans)
  			{
  				subplanstate = node->mt_plans[node->mt_whichplan];
! 				resultRelInfo = node->resultRelInfo + node->mt_whichplan;
! 				junkfilter = resultRelInfo->ri_junkFilter;
  				EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan);
  				continue;
  			}
***************
*** 726,743 **** ExecModifyTable(ModifyTableState *node)
  			if (operation != CMD_DELETE)
  				slot = ExecFilterJunk(junkfilter, slot);
  		}
  
  		switch (operation)
  		{
  			case CMD_INSERT:
! 				slot = ExecInsert(slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(tupleid, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(tupleid, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
--- 727,751 ----
  			if (operation != CMD_DELETE)
  				slot = ExecFilterJunk(junkfilter, slot);
  		}
+ 	
+ 		/*
+ 	 	 * es_result_relation_info must point to the currently active result
+ 	 	 * relation.  We want it to be NULL whenever we're not within
+ 	 	 * ModifyTable, though.
+ 		 */
+ 		estate->es_result_relation_info = resultRelInfo;
  
  		switch (operation)
  		{
  			case CMD_INSERT:
! 				slot = ExecInsert(node->canSetTag, slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(node->canSetTag, tupleid, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(node->canSetTag, tupleid, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
***************
*** 805,829 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
  	mtstate->mt_nplans = nplans;
  	mtstate->operation = operation;
  	/* set up epqstate with dummy subplan pointer for the moment */
  	EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, node->epqParam);
  	mtstate->fireBSTriggers = true;
  
- 	/* For the moment, assume our targets are exactly the global result rels */
- 
  	/*
  	 * 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);
  		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
! 		estate->es_result_relation_info++;
  		i++;
  	}
  	estate->es_result_relation_info = NULL;
--- 813,852 ----
  	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
  	mtstate->mt_nplans = nplans;
  	mtstate->operation = operation;
+ 	mtstate->canSetTag = node->canSetTag;
+ 	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;
  
  	/*
  	 * 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!
  	 */
  	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);
! 
! 		resultRelInfo++;
  		i++;
  	}
  	estate->es_result_relation_info = NULL;
***************
*** 860,867 **** 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;
  		foreach(l, node->returningLists)
  		{
  			List	   *rlist = (List *) lfirst(l);
--- 883,889 ----
  		/*
  		 * Build a projection for each result rel.
  		 */
! 		resultRelInfo = mtstate->resultRelInfo;
  		foreach(l, node->returningLists)
  		{
  			List	   *rlist = (List *) lfirst(l);
***************
*** 960,966 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  
  		if (junk_filter_needed)
  		{
! 			resultRelInfo = estate->es_result_relations;
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
--- 982,988 ----
  
  		if (junk_filter_needed)
  		{
! 			resultRelInfo = mtstate->resultRelInfo;
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
***************
*** 989,995 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  		else
  		{
  			if (operation == CMD_INSERT)
! 				ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc,
  									subplan->targetlist);
  		}
  	}
--- 1011,1017 ----
  		else
  		{
  			if (operation == CMD_INSERT)
! 				ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
  									subplan->targetlist);
  		}
  	}
*** a/src/backend/executor/nodeSubplan.c
--- b/src/backend/executor/nodeSubplan.c
***************
*** 668,673 **** ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
--- 668,675 ----
  	sstate->planstate = (PlanState *) list_nth(estate->es_subplanstates,
  											   subplan->plan_id - 1);
  
+ 	Assert(sstate->planstate != NULL);
+ 
  	/* Initialize subexpressions */
  	sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
  	sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent);
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 84,89 **** _copyPlannedStmt(PlannedStmt *from)
--- 84,90 ----
  	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);
***************
*** 171,177 **** _copyModifyTable(ModifyTable *from)
--- 172,180 ----
  	 * copy remainder of node
  	 */
  	COPY_SCALAR_FIELD(operation);
+ 	COPY_SCALAR_FIELD(canSetTag);
  	COPY_NODE_FIELD(resultRelations);
+ 	COPY_SCALAR_FIELD(resultRelIndex);
  	COPY_NODE_FIELD(plans);
  	COPY_NODE_FIELD(returningLists);
  	COPY_NODE_FIELD(rowMarks);
***************
*** 2260,2265 **** _copyInsertStmt(InsertStmt *from)
--- 2263,2269 ----
  	COPY_NODE_FIELD(cols);
  	COPY_NODE_FIELD(selectStmt);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
***************
*** 2273,2278 **** _copyDeleteStmt(DeleteStmt *from)
--- 2277,2283 ----
  	COPY_NODE_FIELD(usingClause);
  	COPY_NODE_FIELD(whereClause);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
***************
*** 2287,2292 **** _copyUpdateStmt(UpdateStmt *from)
--- 2292,2298 ----
  	COPY_NODE_FIELD(whereClause);
  	COPY_NODE_FIELD(fromClause);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 888,893 **** _equalInsertStmt(InsertStmt *a, InsertStmt *b)
--- 888,894 ----
  	COMPARE_NODE_FIELD(cols);
  	COMPARE_NODE_FIELD(selectStmt);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
***************
*** 899,904 **** _equalDeleteStmt(DeleteStmt *a, DeleteStmt *b)
--- 900,906 ----
  	COMPARE_NODE_FIELD(usingClause);
  	COMPARE_NODE_FIELD(whereClause);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
***************
*** 911,916 **** _equalUpdateStmt(UpdateStmt *a, UpdateStmt *b)
--- 913,919 ----
  	COMPARE_NODE_FIELD(whereClause);
  	COMPARE_NODE_FIELD(fromClause);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 2394,2399 **** bool
--- 2394,2449 ----
  					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;
+ 				if (walker(stmt->withClause, 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;
+ 				if (walker(stmt->withClause, 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;
+ 				if (walker(stmt->withClause, context))
+ 					return true;
+ 			}
+ 			break;
  		case T_A_Expr:
  			{
  				A_Expr	   *expr = (A_Expr *) node;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 327,332 **** _outModifyTable(StringInfo str, ModifyTable *node)
--- 327,333 ----
  
  	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);
***************
*** 1533,1538 **** _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
--- 1534,1540 ----
  	WRITE_NODE_FIELD(finalrowmarks);
  	WRITE_NODE_FIELD(relationOids);
  	WRITE_NODE_FIELD(invalItems);
+ 	WRITE_NODE_FIELD(resultRelations);
  	WRITE_UINT_FIELD(lastPHId);
  	WRITE_BOOL_FIELD(transientPlan);
  }
***************
*** 1548,1554 **** _outPlannerInfo(StringInfo str, PlannerInfo *node)
  	WRITE_UINT_FIELD(query_level);
  	WRITE_NODE_FIELD(join_rel_list);
  	WRITE_INT_FIELD(join_cur_level);
- 	WRITE_NODE_FIELD(resultRelations);
  	WRITE_NODE_FIELD(init_plans);
  	WRITE_NODE_FIELD(cte_plan_ids);
  	WRITE_NODE_FIELD(eq_classes);
--- 1550,1555 ----
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 3754,3760 **** make_result(PlannerInfo *root,
   * to make it look better sometime.
   */
  ModifyTable *
! make_modifytable(CmdType operation, List *resultRelations,
  				 List *subplans, List *returningLists,
  				 List *rowMarks, int epqParam)
  {
--- 3754,3761 ----
   * to make it look better sometime.
   */
  ModifyTable *
! make_modifytable(CmdType operation, bool canSetTag,
! 				 List *resultRelations,
  				 List *subplans, List *returningLists,
  				 List *rowMarks, int epqParam)
  {
***************
*** 3763,3772 **** 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.
  	 */
--- 3764,3769 ----
***************
*** 3804,3809 **** make_modifytable(CmdType operation, List *resultRelations,
--- 3801,3807 ----
  		node->plan.targetlist = NIL;
  
  	node->operation = operation;
+ 	node->canSetTag = canSetTag;
  	node->resultRelations = resultRelations;
  	node->plans = subplans;
  	node->returningLists = returningLists;
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 160,165 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
--- 160,167 ----
  	glob->finalrowmarks = NIL;
  	glob->relationOids = NIL;
  	glob->invalItems = NIL;
+ 	glob->hasWritableCtes = false;
+ 	glob->resultRelations = NIL;
  	glob->lastPHId = 0;
  	glob->transientPlan = false;
  
***************
*** 237,245 **** 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->utilityStmt = parse->utilityStmt;
  	result->intoClause = parse->intoClause;
  	result->subplans = glob->subplans;
  	result->rewindPlanIDs = glob->rewindPlanIDs;
  	result->rowMarks = glob->finalrowmarks;
--- 239,248 ----
  	result->transientPlan = glob->transientPlan;
  	result->planTree = top_plan;
  	result->rtable = glob->finalrtable;
! 	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,547 **** subquery_planner(PlannerGlobal *glob, Query *parse,
  				rowMarks = root->rowMarks;
  
  			plan = (Plan *) make_modifytable(parse->commandType,
! 											 copyObject(root->resultRelations),
  											 list_make1(plan),
  											 returningLists,
  											 rowMarks,
--- 544,551 ----
  				rowMarks = root->rowMarks;
  
  			plan = (Plan *) make_modifytable(parse->commandType,
! 											 parse->canSetTag,
! 											 list_make1_int(parse->resultRelation),
  											 list_make1(plan),
  											 returningLists,
  											 rowMarks,
***************
*** 706,718 **** 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;
  
  	foreach(l, root->append_rel_list)
  	{
--- 710,722 ----
  	Query	   *parse = root->parse;
  	int			parentRTindex = parse->resultRelation;
  	List	   *subplans = 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,779 **** inheritance_planner(PlannerInfo *root)
  		}
  	}
  
- 	root->resultRelations = resultRelations;
- 
  	/* Mark result as unordered (probably unnecessary) */
  	root->query_pathkeys = NIL;
  
--- 776,781 ----
***************
*** 783,789 **** 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,
--- 785,790 ----
***************
*** 818,824 **** 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),
  									 subplans, 
  									 returningLists,
  									 rowMarks,
--- 819,826 ----
  
  	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
  	return (Plan *) make_modifytable(parse->commandType,
! 									 parse->canSetTag,
! 									 resultRelations,
  									 subplans, 
  									 returningLists,
  									 rowMarks,
***************
*** 1668,1679 **** 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.
--- 1670,1675 ----
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 520,525 **** set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
--- 520,529 ----
  											  (Plan *) lfirst(l),
  											  rtoffset);
  				}
+ 
+ 				splan->resultRelIndex = list_length(glob->resultRelations);
+ 				glob->resultRelations = list_concat(glob->resultRelations,
+ 													splan->resultRelations);
  			}
  			break;
  		case T_Append:
*** a/src/backend/optimizer/plan/subselect.c
--- b/src/backend/optimizer/plan/subselect.c
***************
*** 873,888 **** SS_process_ctes(PlannerInfo *root)
  		Bitmapset  *tmpset;
  		int			paramid;
  		Param	   *prm;
  
  		/*
! 		 * Ignore CTEs that are not actually referenced anywhere.
  		 */
! 		if (cte->cterefcount == 0)
  		{
  			/* Make a dummy entry in cte_plan_ids */
  			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
  			continue;
  		}
  
  		/*
  		 * Copy the source Query node.	Probably not necessary, but let's keep
--- 873,905 ----
  		Bitmapset  *tmpset;
  		int			paramid;
  		Param	   *prm;
+ 		CmdType		cmdType = ((Query *) cte->ctequery)->commandType;
  
  		/*
! 		 * Ignore SELECT CTEs that are not actually referenced anywhere.
  		 */
! 		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,904 **** SS_process_ctes(PlannerInfo *root)
--- 916,924 ----
  								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.
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 294,299 **** transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
--- 294,306 ----
  
  	qry->distinctClause = NIL;
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * The USING clause is non-standard SQL syntax, and is equivalent in
  	 * functionality to the FROM list that can be specified for UPDATE. The
***************
*** 346,351 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 353,365 ----
  	qry->commandType = CMD_INSERT;
  	pstate->p_is_insert = true;
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
  	 * VALUES list, or general SELECT input.  We special-case VALUES, both for
***************
*** 370,377 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
  		pstate->p_relnamespace = NIL;
  		sub_varnamespace = pstate->p_varnamespace;
  		pstate->p_varnamespace = NIL;
- 		/* There can't be any outer WITH to worry about */
- 		Assert(pstate->p_ctenamespace == NIL);
  	}
  	else
  	{
--- 384,389 ----
***************
*** 1739,1744 **** transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
--- 1751,1763 ----
  										 true,
  										 ACL_UPDATE);
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * the FROM clause is non-standard SQL syntax. We used to be able to do
  	 * this with REPLACE in POSTQUEL so we keep the feature.
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 428,434 **** static TypeName *TableFuncTypeName(List *columns);
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
! %type <with> 	with_clause
  %type <list>	cte_list
  
  %type <list>	window_clause window_definition_list opt_partition_clause
--- 428,434 ----
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
! %type <with> 	with_clause opt_with_clause
  %type <list>	cte_list
  
  %type <list>	window_clause window_definition_list opt_partition_clause
***************
*** 7033,7043 **** DeallocateStmt: DEALLOCATE name
   *****************************************************************************/
  
  InsertStmt:
! 			INSERT INTO qualified_name insert_rest returning_clause
  				{
! 					$4->relation = $3;
! 					$4->returningList = $5;
! 					$$ = (Node *) $4;
  				}
  		;
  
--- 7033,7044 ----
   *****************************************************************************/
  
  InsertStmt:
! 			opt_with_clause INSERT INTO qualified_name insert_rest returning_clause
  				{
! 					$5->relation = $4;
! 					$5->returningList = $6;
! 					$5->withClause = $1;
! 					$$ = (Node *) $5;
  				}
  		;
  
***************
*** 7093,7106 **** returning_clause:
   *
   *****************************************************************************/
  
! DeleteStmt: DELETE_P FROM relation_expr_opt_alias
  			using_clause where_or_current_clause returning_clause
  				{
  					DeleteStmt *n = makeNode(DeleteStmt);
! 					n->relation = $3;
! 					n->usingClause = $4;
! 					n->whereClause = $5;
! 					n->returningList = $6;
  					$$ = (Node *)n;
  				}
  		;
--- 7094,7108 ----
   *
   *****************************************************************************/
  
! DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias
  			using_clause where_or_current_clause returning_clause
  				{
  					DeleteStmt *n = makeNode(DeleteStmt);
! 					n->relation = $4;
! 					n->usingClause = $5;
! 					n->whereClause = $6;
! 					n->returningList = $7;
! 					n->withClause = $1;
  					$$ = (Node *)n;
  				}
  		;
***************
*** 7155,7172 **** opt_nowait:	NOWAIT							{ $$ = TRUE; }
   *
   *****************************************************************************/
  
! UpdateStmt: UPDATE relation_expr_opt_alias
  			SET set_clause_list
  			from_clause
  			where_or_current_clause
  			returning_clause
  				{
  					UpdateStmt *n = makeNode(UpdateStmt);
! 					n->relation = $2;
! 					n->targetList = $4;
! 					n->fromClause = $5;
! 					n->whereClause = $6;
! 					n->returningList = $7;
  					$$ = (Node *)n;
  				}
  		;
--- 7157,7175 ----
   *
   *****************************************************************************/
  
! UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
  			SET set_clause_list
  			from_clause
  			where_or_current_clause
  			returning_clause
  				{
  					UpdateStmt *n = makeNode(UpdateStmt);
! 					n->relation = $3;
! 					n->targetList = $5;
! 					n->fromClause = $6;
! 					n->whereClause = $7;
! 					n->returningList = $8;
! 					n->withClause = $1;
  					$$ = (Node *)n;
  				}
  		;
***************
*** 7492,7497 **** with_clause:
--- 7495,7504 ----
  			}
  		;
  
+ opt_with_clause:
+ 		with_clause								{ $$ = $1; }
+ 		| /*EMPTY*/								{ $$ = NULL; }
+ 
  cte_list:
  		common_table_expr						{ $$ = list_make1($1); }
  		| cte_list ',' common_table_expr		{ $$ = lappend($1, $3); }
***************
*** 7506,7511 **** common_table_expr:  name opt_name_list AS select_with_parens
--- 7513,7545 ----
  				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:
*** a/src/backend/parser/parse_cte.c
--- b/src/backend/parser/parse_cte.c
***************
*** 18,23 ****
--- 18,24 ----
  #include "nodes/nodeFuncs.h"
  #include "parser/analyze.h"
  #include "parser/parse_cte.h"
+ #include "nodes/plannodes.h"
  #include "utils/builtins.h"
  
  
***************
*** 225,246 **** static void
  analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
  {
  	Query	   *query;
! 
! 	/* Analysis not done already */
! 	Assert(IsA(cte->ctequery, SelectStmt));
  
  	query = parse_sub_analyze(cte->ctequery, pstate, cte, false);
  	cte->ctequery = (Node *) query;
  
  	/*
  	 * 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.)
  	 */
! 	if (!IsA(query, Query) ||
! 		query->commandType != CMD_SELECT ||
! 		query->utilityStmt != NULL)
! 		elog(ERROR, "unexpected non-SELECT command in subquery in WITH");
  	if (query->intoClause)
  		ereport(ERROR,
  				(errcode(ERRCODE_SYNTAX_ERROR),
--- 226,250 ----
  analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
  {
  	Query	   *query;
! 	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.
! 	 * 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.
  	 */
! 	Assert(IsA(query, Query) && query->utilityStmt == NULL);
! 
  	if (query->intoClause)
  		ereport(ERROR,
  				(errcode(ERRCODE_SYNTAX_ERROR),
***************
*** 251,257 **** analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
  	if (!cte->cterecursive)
  	{
  		/* Compute the output column names/types if not done yet */
! 		analyzeCTETargetList(pstate, cte, query->targetList);
  	}
  	else
  	{
--- 255,261 ----
  	if (!cte->cterecursive)
  	{
  		/* Compute the output column names/types if not done yet */
! 		analyzeCTETargetList(pstate, cte, cteList);
  	}
  	else
  	{
***************
*** 269,275 **** analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
  		lctyp = list_head(cte->ctecoltypes);
  		lctypmod = list_head(cte->ctecoltypmods);
  		varattno = 0;
! 		foreach(lctlist, query->targetList)
  		{
  			TargetEntry *te = (TargetEntry *) lfirst(lctlist);
  			Node	   *texpr;
--- 273,279 ----
  		lctyp = list_head(cte->ctecoltypes);
  		lctypmod = list_head(cte->ctecoltypmods);
  		varattno = 0;
! 		foreach(lctlist, cteList)
  		{
  			TargetEntry *te = (TargetEntry *) lfirst(lctlist);
  			Node	   *texpr;
***************
*** 595,606 **** checkWellFormedRecursion(CteState *cstate)
  		CommonTableExpr *cte = cstate->items[i].cte;
  		SelectStmt *stmt = (SelectStmt *) cte->ctequery;
  
- 		Assert(IsA(stmt, SelectStmt));	/* not analyzed yet */
- 
  		/* Ignore items that weren't found to be recursive */
  		if (!cte->cterecursive)
  			continue;
  
  		/* Must have top-level UNION */
  		if (stmt->op != SETOP_UNION)
  			ereport(ERROR,
--- 599,610 ----
  		CommonTableExpr *cte = cstate->items[i].cte;
  		SelectStmt *stmt = (SelectStmt *) cte->ctequery;
  
  		/* Ignore items that weren't found to be recursive */
  		if (!cte->cterecursive)
  			continue;
  
+ 		Assert(IsA(stmt, SelectStmt));	/* not analyzed yet */
+ 
  		/* Must have top-level UNION */
  		if (stmt->op != SETOP_UNION)
  			ereport(ERROR,
*** a/src/backend/parser/parse_relation.c
--- b/src/backend/parser/parse_relation.c
***************
*** 24,29 ****
--- 24,30 ----
  #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"
*** a/src/backend/parser/parse_target.c
--- b/src/backend/parser/parse_target.c
***************
*** 314,323 **** markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 				ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
  									   attnum);
  				if (ste == NULL || ste->resjunk)
  					elog(ERROR, "subquery %s does not have attribute %d",
--- 314,333 ----
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
+ 				List		*cteList;
+ 				Query		*ctequery;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 
! 				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,1355 **** expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 				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);
--- 1355,1374 ----
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
+ 				List		*cteList;
+ 				Query		*ctequery;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 
! 				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,1378 **** expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
  						 levelsup++)
  						pstate = pstate->parentParseState;
  					mypstate.parentParseState = pstate;
! 					mypstate.p_rtable = ((Query *) cte->ctequery)->rtable;
  					/* don't bother filling the rest of the fake pstate */
  
  					return expandRecordVariable(&mypstate, (Var *) expr, 0);
--- 1391,1397 ----
  						 levelsup++)
  						pstate = pstate->parentParseState;
  					mypstate.parentParseState = pstate;
! 					mypstate.p_rtable = ctequery->rtable;
  					/* don't bother filling the rest of the fake pstate */
  
  					return expandRecordVariable(&mypstate, (Var *) expr, 0);
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 1632,1637 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1632,1641 ----
  	bool		returning = false;
  	Query	   *qual_product = NULL;
  	List	   *rewritten = NIL;
+ 	ListCell	*lc1;
+ 	CommonTableExpr	*cte;
+ 	Query		*ctequery;
+ 	List		*newstuff;
  
  	/*
  	 * If the statement is an update, insert or delete - fire rules on it.
***************
*** 1749,1755 **** 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);
--- 1753,1758 ----
***************
*** 1804,1809 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1807,1868 ----
  	}
  
  	/*
+ 	 * Rewrite DML statements inside CTEs.
+ 	 */
+ 	foreach(lc1, parsetree->cteList)
+ 	{
+ 		cte = lfirst(lc1);
+ 
+ 		ctequery = (Query *) cte->ctequery;
+ 
+ 		if (ctequery->commandType == CMD_SELECT)
+ 			continue;
+ 
+ 		newstuff = RewriteQuery(ctequery, NIL);
+ 
+ 		/* Currently we can only handle unconditional DO INSTEAD rules correctly. */
+ 		if (list_length(newstuff) > 1)
+ 		{
+ 			ListCell *lc2;
+ 
+ 			foreach(lc2, newstuff)
+ 			{
+ 				QuerySource qsrc = ((Query *) lfirst(lc2))->querySource;
+ 				
+ 				if (qsrc == QSRC_QUAL_INSTEAD_RULE)
+ 				{
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("Conditional DO INSTEAD rules aren't supported in DML WITH statements")));
+ 				}
+ 				else if (qsrc == QSRC_NON_INSTEAD_RULE)
+ 				{
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("DO ALSO rules aren't supported in DML WITH statements")));
+ 				}
+ 			}
+ 
+ 			elog(ERROR, "unknown rewrite result");
+ 		}
+ 		else if (list_length(newstuff) == 0)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 					 errmsg("DO INSTEAD NOTHING rules aren't supported in DML WITH statements")));
+ 		}
+ 		else
+ 		{
+ 			Assert(list_length(newstuff) == 1);
+ 
+ 			cte->ctequery = (Node *) linitial(newstuff);
+ 
+ 			/* this query won't set the command tag */
+ 			((Query *) cte->ctequery)->canSetTag = false;
+ 		}
+ 	}
+ 
+ 	/*
  	 * 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
*** a/src/backend/tcop/pquery.c
--- b/src/backend/tcop/pquery.c
***************
*** 293,298 **** ChoosePortalStrategy(List *stmts)
--- 293,299 ----
  			if (pstmt->canSetTag)
  			{
  				if (pstmt->commandType == CMD_SELECT &&
+ 					pstmt->hasWritableCtes == false &&
  					pstmt->utilityStmt == NULL &&
  					pstmt->intoClause == NULL)
  					return PORTAL_ONE_SELECT;
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 3978,3986 **** 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);
  
  					if (ste == NULL || ste->resjunk)
  						elog(ERROR, "subquery %s does not have attribute %d",
--- 3978,3993 ----
  				}
  				if (lc != NULL)
  				{
! 					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",
*** a/src/backend/utils/time/snapmgr.c
--- b/src/backend/utils/time/snapmgr.c
***************
*** 104,110 **** bool		FirstSnapshotSet = false;
  static bool registered_serializable = false;
  
  
- static Snapshot CopySnapshot(Snapshot snapshot);
  static void FreeSnapshot(Snapshot snapshot);
  static void SnapshotResetXmin(void);
  
--- 104,109 ----
***************
*** 192,198 **** SnapshotSetCommandId(CommandId curcid)
   * The copy is palloc'd in TopTransactionContext and has initial refcounts set
   * to 0.  The returned snapshot has the copied flag set.
   */
! static Snapshot
  CopySnapshot(Snapshot snapshot)
  {
  	Snapshot	newsnap;
--- 191,197 ----
   * The copy is palloc'd in TopTransactionContext and has initial refcounts set
   * to 0.  The returned snapshot has the copied flag set.
   */
! Snapshot
  CopySnapshot(Snapshot snapshot)
  {
  	Snapshot	newsnap;
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 160,166 **** extern void ExecutorRewind(QueryDesc *queryDesc);
  extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
- 				  CmdType operation,
  				  int instrument_options);
  extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
  extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
--- 160,165 ----
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 376,381 **** typedef struct EState
--- 376,382 ----
  
  	List	   *es_exprcontexts;	/* List of ExprContexts within EState */
  
+ 	List	   *prescans;
  	List	   *es_subplanstates;		/* List of PlanState for SubPlans */
  
  	/*
***************
*** 1026,1034 **** typedef struct ModifyTableState
--- 1027,1038 ----
  {
  	PlanState		ps;				/* its first field is NodeTag */
  	CmdType			operation;
+ 	bool			canSetTag;		/* do we set the command tag? */
  	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;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 879,884 **** typedef struct InsertStmt
--- 879,885 ----
  	List	   *cols;			/* optional: names of the target columns */
  	Node	   *selectStmt;		/* the source SELECT/VALUES, or NULL */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } InsertStmt;
  
  /* ----------------------
***************
*** 892,897 **** typedef struct DeleteStmt
--- 893,899 ----
  	List	   *usingClause;	/* optional using clause for more tables */
  	Node	   *whereClause;	/* qualifications */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } DeleteStmt;
  
  /* ----------------------
***************
*** 906,911 **** typedef struct UpdateStmt
--- 908,914 ----
  	Node	   *whereClause;	/* qualifications */
  	List	   *fromClause;		/* optional from clause for more tables */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } UpdateStmt;
  
  /* ----------------------
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 55,60 **** typedef struct PlannedStmt
--- 55,62 ----
  
  	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 */
***************
*** 164,170 **** typedef struct ModifyTable
--- 166,174 ----
  {
  	Plan		plan;
  	CmdType		operation;			/* INSERT, UPDATE, or DELETE */
+ 	bool		canSetTag;			/* do we set the command tag? */
  	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) */
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
***************
*** 80,85 **** typedef struct PlannerGlobal
--- 80,89 ----
  
  	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? */
***************
*** 152,159 **** typedef struct PlannerInfo
  	List	  **join_rel_level;	/* lists of join-relation RelOptInfos */
  	int			join_cur_level;	/* index of list being extended */
  
- 	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 */
--- 156,161 ----
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
***************
*** 77,83 **** extern SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
  		   long numGroups, double outputRows);
  extern Result *make_result(PlannerInfo *root, List *tlist,
  			Node *resconstantqual, Plan *subplan);
! extern ModifyTable *make_modifytable(CmdType operation, List *resultRelations,
  									 List *subplans, List *returningLists,
  									 List *rowMarks, int epqParam);
  extern bool is_projection_capable_plan(Plan *plan);
--- 77,84 ----
  		   long numGroups, double outputRows);
  extern Result *make_result(PlannerInfo *root, List *tlist,
  			Node *resconstantqual, Plan *subplan);
! extern ModifyTable *make_modifytable(CmdType operation, bool canSetTag,
! 									 List *resultRelations,
  									 List *subplans, List *returningLists,
  									 List *rowMarks, int epqParam);
  extern bool is_projection_capable_plan(Plan *plan);
*** a/src/include/utils/snapmgr.h
--- b/src/include/utils/snapmgr.h
***************
*** 16,21 ****
--- 16,22 ----
  #include "utils/resowner.h"
  #include "utils/snapshot.h"
  
+ extern Snapshot CopySnapshot(Snapshot snapshot);
  
  extern bool FirstSnapshotSet;
  
*** a/src/test/regress/expected/with.out
--- b/src/test/regress/expected/with.out
***************
*** 1026,1028 **** SELECT * FROM t;
--- 1026,1159 ----
   10
  (55 rows)
  
+ --
+ -- Writeable CTEs
+ --
+ 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)
+ 
+ WITH t AS (
+ 	UPDATE y SET a = a-11
+ ), t2 AS (
+ 	DELETE FROM y WHERE a <= 5
+ )
+ UPDATE y SET a=a+1 RETURNING *;
+  a  
+ ----
+   7
+   8
+   9
+  10
+  11
+ (5 rows)
+ 
+ WITH t AS (
+ 	UPDATE y SET a=a-100
+ ), t2 AS (
+ 	UPDATE y SET a=a+94
+ )
+ SELECT * FROM y;
+  a 
+ ---
+  1
+  2
+  3
+  4
+  5
+ (5 rows)
+ 
+ WITH RECURSIVE t AS (
+ 	INSERT INTO y
+ 		SELECT * FROM t2 WHERE a < 10
+ ), t2 AS (
+ 	UPDATE y SET a=a+6 RETURNING *
+ )
+ SELECT * FROM y;
+  a  
+ ----
+   7
+   8
+   9
+  10
+  11
+   7
+   8
+   9
+ (8 rows)
+ 
*** a/src/test/regress/sql/with.sql
--- b/src/test/regress/sql/with.sql
***************
*** 500,502 **** WITH RECURSIVE t(j) AS (
--- 500,559 ----
      SELECT j+1 FROM t WHERE j < 10
  )
  SELECT * FROM t;
+ 
+ --
+ -- Writeable CTEs
+ --
+ 
+ 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;
+ 
+ WITH t AS (
+ 	UPDATE y SET a = a-11
+ ), t2 AS (
+ 	DELETE FROM y WHERE a <= 5
+ )
+ UPDATE y SET a=a+1 RETURNING *;
+ 
+ WITH t AS (
+ 	UPDATE y SET a=a-100
+ ), t2 AS (
+ 	UPDATE y SET a=a+94
+ )
+ SELECT * FROM y;
+ 
+ WITH RECURSIVE t AS (
+ 	INSERT INTO y
+ 		SELECT * FROM t2 WHERE a < 10
+ ), t2 AS (
+ 	UPDATE y SET a=a+6 RETURNING *
+ )
+ SELECT * FROM y;