writeablectev1.patch

text/plain

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

Patch

Format: context
File+
doc/src/sgml/queries.sgml 30 0
doc/src/sgml/ref/select.sgml 1 0
src/backend/commands/copy.c 2 0
src/backend/commands/portalcmds.c 5 0
src/backend/commands/vacuum.c 2 0
src/backend/commands/view.c 13 0
src/backend/executor/execMain.c 89 0
src/backend/executor/execUtils.c 2 0
src/backend/executor/nodeModifyTable.c 48 0
src/backend/nodes/copyfuncs.c 2 0
src/backend/nodes/nodeFuncs.c 44 0
src/backend/nodes/outfuncs.c 2 0
src/backend/optimizer/plan/createplan.c 0 0
src/backend/optimizer/plan/planner.c 7 0
src/backend/optimizer/plan/setrefs.c 4 0
src/backend/optimizer/plan/subselect.c 22 0
src/backend/parser/gram.y 27 0
src/backend/parser/parse_cte.c 14 0
src/backend/parser/parse_relation.c 1 0
src/backend/parser/parse_target.c 23 0
src/backend/rewrite/rewriteHandler.c 53 0
src/backend/tcop/pquery.c 1 0
src/backend/utils/adt/ruleutils.c 10 0
src/include/executor/executor.h 2 0
src/include/nodes/execnodes.h 2 0
src/include/nodes/plannodes.h 3 0
src/include/nodes/relation.h 4 0
src/test/regress/expected/with.out 82 0
src/test/regress/sql/with.sql 35 0
*** a/doc/src/sgml/queries.sgml
--- b/doc/src/sgml/queries.sgml
***************
*** 1499,1505 **** SELECT 3, 'three';
  <synopsis>
  SELECT <replaceable>select_list</replaceable> FROM <replaceable>table_expression</replaceable>
  </synopsis>
!    and can appear anywhere a <literal>SELECT</> can.  For example, you can
     use it as part of a <literal>UNION</>, or attach a
     <replaceable>sort_specification</replaceable> (<literal>ORDER BY</>,
     <literal>LIMIT</>, and/or <literal>OFFSET</>) to it.  <literal>VALUES</>
--- 1499,1505 ----
  <synopsis>
  SELECT <replaceable>select_list</replaceable> FROM <replaceable>table_expression</replaceable>
  </synopsis>
!    and can appear anywhere a <literal>SELECT</literal> can.  For example, you can
     use it as part of a <literal>UNION</>, or attach a
     <replaceable>sort_specification</replaceable> (<literal>ORDER BY</>,
     <literal>LIMIT</>, and/or <literal>OFFSET</>) to it.  <literal>VALUES</>
***************
*** 1529,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,1590 ----
    </para>
  
    <para>
+   A <literal>WITH</literal> clause can also have an
+   <literal>INSERT</literal>, <literal>UPDATE</literal> or
+   <literal>DELETE</literal> (each optionally with a
+   <literal>RETURNING</literal> clause) statement in it.  The example below
+   moves rows from the main table, foo_log into a partition,
+   foo_log_200910.
+ 
+ <programlisting>
+ WITH rows AS (
+         DELETE FROM ONLY foo_log
+         WHERE
+            foo_date &gt;= '2009-10-01' AND
+            foo_date &lt;  '2009-11-01'
+            RETURNING *
+      ), t AS (
+            INSERT INTO foo_log_200910
+            SELECT * FROM rows
+      )
+ VALUES(true);
+ </programlisting>
+ 
+   </para>
+ 
+   <para>
     The optional <literal>RECURSIVE</> modifier changes <literal>WITH</>
     from a mere syntactic convenience into a feature that accomplishes
     things not otherwise possible in standard SQL.  Using
*** 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/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2160,2166 **** CopyFrom(CopyState cstate)
  			heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
  
  			if (resultRelInfo->ri_NumIndices > 0)
! 				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
  													   estate, false);
  
  			/* AFTER ROW INSERT Triggers */
--- 2160,2167 ----
  			heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
  
  			if (resultRelInfo->ri_NumIndices > 0)
! 				recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
! 													   slot, &(tuple->t_self),
  													   estate, false);
  
  			/* AFTER ROW INSERT Triggers */
*** 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/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 3088,3094 **** move_chain_tuple(Relation rel,
  	if (ec->resultRelInfo->ri_NumIndices > 0)
  	{
  		ExecStoreTuple(&newtup, ec->slot, InvalidBuffer, false);
! 		ExecInsertIndexTuples(ec->slot, &(newtup.t_self), ec->estate, true);
  		ResetPerTupleExprContext(ec->estate);
  	}
  }
--- 3088,3094 ----
  	if (ec->resultRelInfo->ri_NumIndices > 0)
  	{
  		ExecStoreTuple(&newtup, ec->slot, InvalidBuffer, false);
! 		ExecInsertIndexTuples(ec->resultRelInfo, ec->slot, &(newtup.t_self), ec->estate, true);
  		ResetPerTupleExprContext(ec->estate);
  	}
  }
***************
*** 3214,3220 **** move_plain_tuple(Relation rel,
  	if (ec->resultRelInfo->ri_NumIndices > 0)
  	{
  		ExecStoreTuple(&newtup, ec->slot, InvalidBuffer, false);
! 		ExecInsertIndexTuples(ec->slot, &(newtup.t_self), ec->estate, true);
  		ResetPerTupleExprContext(ec->estate);
  	}
  }
--- 3214,3220 ----
  	if (ec->resultRelInfo->ri_NumIndices > 0)
  	{
  		ExecStoreTuple(&newtup, ec->slot, InvalidBuffer, false);
! 		ExecInsertIndexTuples(ec->resultRelInfo, ec->slot, &(newtup.t_self), ec->estate, true);
  		ResetPerTupleExprContext(ec->estate);
  	}
  }
*** 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
***************
*** 924,939 **** 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);
  }
  
  /*
--- 924,929 ----
***************
*** 1174,1179 **** ExecutePlan(EState *estate,
--- 1164,1251 ----
  	 */
  	estate->es_direction = direction;
  
+ 	/* Process top-level CTEs in case they have writes inside */
+ 	if (estate->es_plannedstmt->hasWritableCtes)
+ 	{
+ 		ListCell *lc;
+ 
+ 		foreach(lc, estate->es_plannedstmt->planTree->initPlan)
+ 		{
+ 			SubPlan *sp;
+ 			int cte_param_id;
+ 			ParamExecData* prmdata;
+ 			CteScanState *leader;
+ 
+ 			sp = (SubPlan *) lfirst(lc);
+ 			if (sp->subLinkType != CTE_SUBLINK)
+ 				continue;
+ 
+ 			cte_param_id = linitial_int(sp->setParam);
+ 			prmdata = &(estate->es_param_exec_vals[cte_param_id]);
+ 			leader = (CteScanState *) DatumGetPointer(prmdata->value);
+ 
+ 
+ 			/*
+ 			 * bump CID.
+ 			 *
+ 			 * We're currently relying on the fact that there can only be
+ 			 * a SELECT or VALUES as the top-level statement.
+ 			 *
+ 			 * XXX we should probably update the snapshot a bit differently
+ 			 */
+ 			CommandCounterIncrement();
+ 			estate->es_output_cid = GetCurrentCommandId(true);
+ 			estate->es_snapshot->curcid = estate->es_output_cid;
+ 
+ 			/*
+ 			 * If there's no leader, the CTE isn't referenced anywhere
+ 			 * so we can just go ahead and scan the plan
+ 			 */
+ 			if (!leader)
+ 			{
+ 				TupleTableSlot *slot;
+ 				PlanState *ps = (PlanState *) list_nth(estate->es_subplanstates,
+ 													   sp->plan_id - 1);
+ 
+ 				Assert(IsA(ps, ModifyTableState));
+ 
+ 				/*
+ 				 * We might have a RETURNING here, which means that
+ 				 * we have to loop until the plan returns NULL.
+ 				 */
+ 				for (;;)
+ 				{
+ 					slot = ExecProcNode(ps);
+ 					if (TupIsNull(slot))
+ 						break;
+ 				}
+ 			}
+ 			else
+ 			{
+ 				TupleTableSlot* slot;
+ 				PlanState *ps = (PlanState *) list_nth(estate->es_subplanstates,
+ 													   sp->plan_id - 1);
+ 
+ 				/* Regular CTE, ignore */
+ 				if (!IsA(ps, ModifyTableState))
+ 					continue;
+ 
+ 				/*
+ 				 * Scan through the leader CTE so the RETURNING tuples are
+ 				 * stored into the tuple store.
+ 				 */
+ 				for (;;)
+ 				{
+ 					slot = ExecProcNode((PlanState *) leader);
+ 					if (TupIsNull(slot))
+ 						break;
+ 				}
+ 
+ 				ExecReScan((PlanState *) leader, NULL);
+ 			}
+ 		}
+ 	}
+ 
  	/*
  	 * Loop until we've processed the proper number of tuples from the plan.
  	 */
***************
*** 1943,1949 **** 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)
--- 2015,2022 ----
  	 * 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)
***************
*** 1951,1957 **** 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);
--- 2024,2034 ----
  		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/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 968,980 **** ExecCloseIndices(ResultRelInfo *resultRelInfo)
   * ----------------------------------------------------------------
   */
  List *
! ExecInsertIndexTuples(TupleTableSlot *slot,
  					  ItemPointer tupleid,
  					  EState *estate,
  					  bool is_vacuum_full)
  {
  	List	   *result = NIL;
- 	ResultRelInfo *resultRelInfo;
  	int			i;
  	int			numIndices;
  	RelationPtr relationDescs;
--- 968,980 ----
   * ----------------------------------------------------------------
   */
  List *
! ExecInsertIndexTuples(ResultRelInfo* resultRelInfo,
! 					  TupleTableSlot *slot,
  					  ItemPointer tupleid,
  					  EState *estate,
  					  bool is_vacuum_full)
  {
  	List	   *result = NIL;
  	int			i;
  	int			numIndices;
  	RelationPtr relationDescs;
***************
*** 987,993 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  	/*
  	 * Get information from the result relation info structure.
  	 */
- 	resultRelInfo = estate->es_result_relation_info;
  	numIndices = resultRelInfo->ri_NumIndices;
  	relationDescs = resultRelInfo->ri_IndexRelationDescs;
  	indexInfoArray = resultRelInfo->ri_IndexRelationInfo;
--- 987,992 ----
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 158,169 **** ExecProcessReturning(ProjectionInfo *projectReturning,
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecInsert(TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EState *estate)
  {
  	HeapTuple	tuple;
- 	ResultRelInfo *resultRelInfo;
  	Relation	resultRelationDesc;
  	Oid			newId;
  	List	   *recheckIndexes = NIL;
--- 158,169 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecInsert(ResultRelInfo *resultRelInfo,
! 		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EState *estate)
  {
  	HeapTuple	tuple;
  	Relation	resultRelationDesc;
  	Oid			newId;
  	List	   *recheckIndexes = NIL;
***************
*** 177,183 **** ExecInsert(TupleTableSlot *slot,
  	/*
  	 * get information on the (current) result relation
  	 */
- 	resultRelInfo = estate->es_result_relation_info;
  	resultRelationDesc = resultRelInfo->ri_RelationDesc;
  
  	/*
--- 177,182 ----
***************
*** 247,253 **** ExecInsert(TupleTableSlot *slot,
  	 * insert index entries for tuple
  	 */
  	if (resultRelInfo->ri_NumIndices > 0)
! 		recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
  											   estate, false);
  
  	/* AFTER ROW INSERT Triggers */
--- 246,253 ----
  	 * insert index entries for tuple
  	 */
  	if (resultRelInfo->ri_NumIndices > 0)
! 		recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
! 											   slot, &(tuple->t_self),
  											   estate, false);
  
  	/* AFTER ROW INSERT Triggers */
***************
*** 271,282 **** ExecInsert(TupleTableSlot *slot,
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(ItemPointer tupleid,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
  {
- 	ResultRelInfo *resultRelInfo;
  	Relation	resultRelationDesc;
  	HTSU_Result result;
  	ItemPointerData update_ctid;
--- 271,282 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(ResultRelInfo *resultRelInfo,
! 		   ItemPointer tupleid,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
  {
  	Relation	resultRelationDesc;
  	HTSU_Result result;
  	ItemPointerData update_ctid;
***************
*** 285,291 **** ExecDelete(ItemPointer tupleid,
  	/*
  	 * get information on the (current) result relation
  	 */
- 	resultRelInfo = estate->es_result_relation_info;
  	resultRelationDesc = resultRelInfo->ri_RelationDesc;
  
  	/* BEFORE ROW DELETE Triggers */
--- 285,290 ----
***************
*** 414,427 **** ldelete:;
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(ItemPointer tupleid,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
  {
  	HeapTuple	tuple;
- 	ResultRelInfo *resultRelInfo;
  	Relation	resultRelationDesc;
  	HTSU_Result result;
  	ItemPointerData update_ctid;
--- 413,426 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(ResultRelInfo *resultRelInfo,
! 		   ItemPointer tupleid,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
  {
  	HeapTuple	tuple;
  	Relation	resultRelationDesc;
  	HTSU_Result result;
  	ItemPointerData update_ctid;
***************
*** 443,449 **** ExecUpdate(ItemPointer tupleid,
  	/*
  	 * get information on the (current) result relation
  	 */
- 	resultRelInfo = estate->es_result_relation_info;
  	resultRelationDesc = resultRelInfo->ri_RelationDesc;
  
  	/* BEFORE ROW UPDATE Triggers */
--- 442,447 ----
***************
*** 561,567 **** lreplace:;
  	 * If it's a HOT update, we mustn't insert new index entries.
  	 */
  	if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
! 		recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
  											   estate, false);
  
  	/* AFTER ROW UPDATE Triggers */
--- 559,566 ----
  	 * If it's a HOT update, we mustn't insert new index entries.
  	 */
  	if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
! 		recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
! 											   slot, &(tuple->t_self),
  											   estate, false);
  
  	/* AFTER ROW UPDATE Triggers */
***************
*** 587,601 **** 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");
--- 586,600 ----
  	{
  		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");
***************
*** 613,627 **** 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");
--- 612,626 ----
  	{
  		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");
***************
*** 643,648 **** ExecModifyTable(ModifyTableState *node)
--- 642,648 ----
  	EState *estate = node->ps.state;
  	CmdType operation = node->operation;
  	PlanState *subplanstate;
+ 	ResultRelInfo *resultRelInfo;
  	JunkFilter *junkfilter;
  	TupleTableSlot *slot;
  	TupleTableSlot *planSlot;
***************
*** 658,674 **** 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
--- 658,667 ----
  		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
***************
*** 684,692 **** 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;
  			}
--- 677,685 ----
  			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;
  			}
***************
*** 728,741 **** ExecModifyTable(ModifyTableState *node)
  		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:
--- 721,737 ----
  		switch (operation)
  		{
  			case CMD_INSERT:
! 				slot = ExecInsert(resultRelInfo,
! 								  slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(resultRelInfo,
! 								  tupleid, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(resultRelInfo,
! 								  tupleid, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
***************
*** 748,762 **** ExecModifyTable(ModifyTableState *node)
  		 * the work on next call.
  		 */
  		if (slot)
- 		{
- 			estate->es_result_relation_info = NULL;
  			return slot;
- 		}
  	}
  
- 	/* Reset es_result_relation_info before exiting */
- 	estate->es_result_relation_info = NULL;
- 
  	/*
  	 * We're done, but fire AFTER STATEMENT triggers before exiting.
  	 */
--- 744,752 ----
***************
*** 803,827 **** 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;
--- 793,831 ----
  	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;
***************
*** 858,865 **** 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);
--- 862,868 ----
  		/*
  		 * Build a projection for each result rel.
  		 */
! 		resultRelInfo = mtstate->resultRelInfo;
  		foreach(l, node->returningLists)
  		{
  			List	   *rlist = (List *) lfirst(l);
***************
*** 958,964 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  
  		if (junk_filter_needed)
  		{
! 			resultRelInfo = estate->es_result_relations;
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
--- 961,967 ----
  
  		if (junk_filter_needed)
  		{
! 			resultRelInfo = mtstate->resultRelInfo;
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
***************
*** 987,993 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  		else
  		{
  			if (operation == CMD_INSERT)
! 				ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc,
  									subplan->targetlist);
  		}
  	}
--- 990,996 ----
  		else
  		{
  			if (operation == CMD_INSERT)
! 				ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
  									subplan->targetlist);
  		}
  	}
*** 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);
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 2383,2388 **** bool
--- 2383,2432 ----
  					return true;
  			}
  			break;
+ 		case T_InsertStmt:
+ 			{
+ 				InsertStmt *stmt = (InsertStmt *) node;
+ 
+ 				if (walker(stmt->relation, context))
+ 					return true;
+ 				if (walker(stmt->cols, context))
+ 					return true;
+ 				if (walker(stmt->selectStmt, context))
+ 					return true;
+ 				if (walker(stmt->returningList, context))
+ 					return true;
+ 			}
+ 			break;
+ 		case T_UpdateStmt:
+ 			{
+ 				UpdateStmt *stmt = (UpdateStmt *) node;
+ 
+ 				if (walker(stmt->relation, context))
+ 					return true;
+ 				if (walker(stmt->targetList, context))
+ 					return true;
+ 				if (walker(stmt->whereClause, context))
+ 					return true;
+ 				if (walker(stmt->fromClause, context))
+ 					return true;
+ 				if (walker(stmt->returningList, context))
+ 					return true;
+ 			}
+ 			break;
+ 		case T_DeleteStmt:
+ 			{
+ 				DeleteStmt *stmt = (DeleteStmt *) node;
+ 
+ 				if (walker(stmt->relation, context))
+ 					return true;
+ 				if (walker(stmt->usingClause, context))
+ 					return true;
+ 				if (walker(stmt->whereClause, context))
+ 					return true;
+ 				if (walker(stmt->returningList, context))
+ 					return true;
+ 			}
+ 			break;
  		case T_A_Expr:
  			{
  				A_Expr	   *expr = (A_Expr *) node;
*** 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);
***************
*** 1529,1534 **** _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
--- 1530,1536 ----
  	WRITE_NODE_FIELD(finalrowmarks);
  	WRITE_NODE_FIELD(relationOids);
  	WRITE_NODE_FIELD(invalItems);
+ 	WRITE_NODE_FIELD(resultRelations);
  	WRITE_UINT_FIELD(lastPHId);
  	WRITE_BOOL_FIELD(transientPlan);
  }
***************
*** 1543,1549 **** _outPlannerInfo(StringInfo str, PlannerInfo *node)
  	WRITE_NODE_FIELD(glob);
  	WRITE_UINT_FIELD(query_level);
  	WRITE_NODE_FIELD(join_rel_list);
- 	WRITE_NODE_FIELD(resultRelations);
  	WRITE_NODE_FIELD(init_plans);
  	WRITE_NODE_FIELD(cte_plan_ids);
  	WRITE_NODE_FIELD(eq_classes);
--- 1545,1550 ----
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 3759,3768 **** 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.
  	 */
--- 3759,3764 ----
*** 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,
***************
*** 1667,1678 **** 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.
--- 1667,1672 ----
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 516,521 **** set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
--- 516,525 ----
  											  (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/gram.y
--- b/src/backend/parser/gram.y
***************
*** 7374,7379 **** common_table_expr:  name opt_name_list AS select_with_parens
--- 7374,7406 ----
  				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	*lc;
+ 	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,1861 ----
  	}
  
  	/*
+ 	 * Rewrite DML statements inside CTEs.  If there are any
+ 	 * DO ALSO rules, they are added to the top level (there
+ 	 * won't be any non-top-level CTEs with DML).
+ 	 */
+ 	foreach(lc, parsetree->cteList)
+ 	{
+ 		cte = lfirst(lc);
+ 
+ 		ctequery = (Query *) cte->ctequery;
+ 
+ 		if (ctequery->commandType == CMD_SELECT)
+ 			continue;
+ 
+ 		newstuff = RewriteQuery(ctequery, NIL);
+ 
+ 		/*
+ 		 * For UPDATE and DELETE, the actual query is
+ 		 * added to the end of the list.
+ 		 */
+ 		if (list_length(newstuff) > 1 &&
+ 			ctequery->commandType != CMD_INSERT)
+ 		{
+ 			ListCell	*lc;
+ 			int n = 1;
+ 
+ 			foreach(lc, newstuff)
+ 			{
+ 				/*
+ 				 * If this is the last one, don't add it to the results.
+ 				 * Instead, update the query inside the CTE.
+ 				 */
+ 				if (n == list_length(newstuff))
+ 					cte->ctequery = (Node *) lfirst(lc);
+ 				else
+ 					rewritten = lcons((void *) lfirst(lc), rewritten);
+ 					
+ 				n++;
+ 			}
+ 
+ 		}
+ 		else
+ 		{
+ 			cte->ctequery = (Node *) linitial(newstuff);
+ 			rewritten = list_concat(rewritten,
+ 							  list_delete_first(newstuff));
+ 		}
+ 	}
+ 
+ 	/*
  	 * For INSERTs, the original query is done first; for UPDATE/DELETE, it is
  	 * done last.  This is needed because update and delete rule actions might
  	 * not do anything if they are invoked after the update or delete is
*** 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
***************
*** 3858,3866 **** 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",
--- 3858,3873 ----
  				}
  				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/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 319,325 **** extern void ExecCloseScanRelation(Relation scanrel);
  
  extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
  extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
! extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
  					  EState *estate, bool is_vacuum_full);
  
  extern void RegisterExprContextCallback(ExprContext *econtext,
--- 319,326 ----
  
  extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
  extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
! extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
! 					  TupleTableSlot *slot, ItemPointer tupleid,
  					  EState *estate, bool is_vacuum_full);
  
  extern void RegisterExprContextCallback(ExprContext *econtext,
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 1021,1026 **** typedef struct ModifyTableState
--- 1021,1028 ----
  	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/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? */
***************
*** 142,149 **** typedef struct PlannerInfo
  	List	   *join_rel_list;	/* list of join-relation RelOptInfos */
  	struct HTAB *join_rel_hash; /* optional hashtable for join relations */
  
- 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
- 
  	List	   *init_plans;		/* init SubPlans for query */
  
  	List	   *cte_plan_ids;	/* per-CTE-item list of subplan IDs */
--- 146,151 ----
*** a/src/test/regress/expected/with.out
--- b/src/test/regress/expected/with.out
***************
*** 1026,1028 **** SELECT * FROM t;
--- 1026,1110 ----
   10
  (55 rows)
  
+ --
+ -- Writeable CTEs with RETURNING
+ --
+ WITH t AS (
+     INSERT INTO y
+     VALUES
+         (11),
+         (12),
+         (13),
+         (14),
+         (15),
+         (16),
+         (17),
+         (18),
+         (19),
+         (20)
+     RETURNING *
+ )
+ SELECT * FROM t;
+  a  
+ ----
+  11
+  12
+  13
+  14
+  15
+  16
+  17
+  18
+  19
+  20
+ (10 rows)
+ 
+ WITH t AS (
+     UPDATE y
+     SET a=a+1
+     RETURNING *
+ )
+ SELECT * FROM t;
+  a  
+ ----
+   2
+   3
+   4
+   5
+   6
+   7
+   8
+   9
+  10
+  11
+  12
+  13
+  14
+  15
+  16
+  17
+  18
+  19
+  20
+  21
+ (20 rows)
+ 
+ WITH t AS (
+     DELETE FROM y
+     WHERE a <= 10
+     RETURNING *
+ )
+ SELECT * FROM t;
+  a  
+ ----
+   2
+   3
+   4
+   5
+   6
+   7
+   8
+   9
+  10
+ (9 rows)
+ 
*** a/src/test/regress/sql/with.sql
--- b/src/test/regress/sql/with.sql
***************
*** 500,502 **** WITH RECURSIVE t(j) AS (
--- 500,537 ----
      SELECT j+1 FROM t WHERE j < 10
  )
  SELECT * FROM t;
+ 
+ --
+ -- Writeable CTEs with RETURNING
+ --
+ 
+ WITH t AS (
+     INSERT INTO y
+     VALUES
+         (11),
+         (12),
+         (13),
+         (14),
+         (15),
+         (16),
+         (17),
+         (18),
+         (19),
+         (20)
+     RETURNING *
+ )
+ SELECT * FROM t;
+ 
+ WITH t AS (
+     UPDATE y
+     SET a=a+1
+     RETURNING *
+ )
+ SELECT * FROM t;
+ 
+ WITH t AS (
+     DELETE FROM y
+     WHERE a <= 10
+     RETURNING *
+ )
+ SELECT * FROM t;