*** a/doc/src/sgml/queries.sgml
--- b/doc/src/sgml/queries.sgml
***************
*** 1529,1538 **** SELECT select_list FROM table_expression
    
  
    
!    WITH> provides a way to write subqueries for use in a larger
!    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:
  
  
  WITH regional_sales AS (
--- 1529,1539 ----
    
  
    
!    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:
  
  
  WITH regional_sales AS (
***************
*** 1560,1565 **** GROUP BY region, product;
--- 1561,1588 ----
    
  
    
+   A WITH clause can also have an
+   INSERT, UPDATE or
+   DELETE (each optionally with a
+   RETURNING clause) statement in it.  The example below
+   moves rows from the main table, foo_log into a partition,
+   foo_log_200910.
+ 
+ 
+ WITH rows AS (
+         DELETE FROM ONLY foo_log
+         WHERE
+            foo_date >= '2009-10-01' AND
+            foo_date <  '2009-11-01'
+         RETURNING *
+      )
+ INSERT INTO foo_log_200910
+ SELECT * FROM rows
+ 
+ 
+   
+ 
+   
     The optional RECURSIVE> modifier changes 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 ----
  
   
  
+ [ WITH [ RECURSIVE ] with_query [, ...] ]
  DELETE FROM [ ONLY ] table [ [ AS ] alias ]
      [ USING using_list ]
      [ WHERE condition | WHERE CURRENT OF cursor_name ]
*** a/doc/src/sgml/ref/insert.sgml
--- b/doc/src/sgml/ref/insert.sgml
***************
*** 21,26 **** PostgreSQL documentation
--- 21,27 ----
  
   
  
+ [ WITH [ RECURSIVE ] with_query [, ...] ]
  INSERT INTO table [ ( column [, ...] ) ]
      { DEFAULT VALUES | VALUES ( { expression | DEFAULT } [, ...] ) [, ...] | query }
      [ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ]
*** a/doc/src/sgml/ref/select.sgml
--- b/doc/src/sgml/ref/select.sgml
***************
*** 58,64 **** SELECT [ ALL | DISTINCT [ ON ( expressionand with_query is:
  
!     with_query_name [ ( column_name [, ...] ) ] AS ( select )
  
  TABLE { [ ONLY ] table_name [ * ] | with_query_name }
  
--- 58,64 ----
  
  and with_query is:
  
!     with_query_name [ ( column_name [, ...] ) ] AS ( select | (insert | update | delete [ RETURNING...]))
  
  TABLE { [ ONLY ] table_name [ * ] | with_query_name }
  
*** a/doc/src/sgml/ref/update.sgml
--- b/doc/src/sgml/ref/update.sgml
***************
*** 21,26 **** PostgreSQL documentation
--- 21,27 ----
  
   
  
+ [ WITH [ RECURSIVE ] with_query [, ...] ]
  UPDATE [ ONLY ] table [ [ AS ] alias ]
      SET { column = { expression | DEFAULT } |
            ( column [, ...] ) = ( { expression | 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  xml_whitespace_option
  
  %type  	common_table_expr
! %type  	with_clause
  %type 	cte_list
  
  %type 	window_clause window_definition_list opt_partition_clause
--- 428,434 ----
  %type  xml_whitespace_option
  
  %type  	common_table_expr
! %type  	with_clause opt_with_clause
  %type 	cte_list
  
  %type 	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;