wcte_20101222.diff

application/x-patch

Filename: wcte_20101222.diff
Type: application/x-patch
Part: 0
Message: Re: wCTE behaviour

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: context
File+
doc/src/sgml/queries.sgml 21 0
doc/src/sgml/ref/create_rule.sgml 11 0
doc/src/sgml/ref/delete.sgml 1 0
doc/src/sgml/ref/select.sgml 42 0
doc/src/sgml/ref/update.sgml 1 0
src/backend/commands/portalcmds.c 5 0
src/backend/commands/tablecmds.c 0 0
src/backend/commands/view.c 13 0
src/backend/executor/execMain.c 95 0
src/backend/executor/nodeModifyTable.c 98 0
src/backend/executor/nodeSubplan.c 2 0
src/backend/nodes/copyfuncs.c 3 0
src/backend/nodes/nodeFuncs.c 50 0
src/backend/nodes/outfuncs.c 2 0
src/backend/optimizer/plan/createplan.c 3 0
src/backend/optimizer/plan/planner.c 8 0
src/backend/optimizer/plan/setrefs.c 4 0
src/backend/optimizer/plan/subselect.c 18 0
src/backend/parser/gram.y 2 0
src/backend/parser/parse_cte.c 31 0
src/backend/parser/parse_target.c 23 0
src/backend/rewrite/rewriteHandler.c 69 0
src/backend/tcop/pquery.c 1 0
src/backend/tcop/utility.c 2 0
src/backend/utils/adt/ruleutils.c 10 0
src/include/executor/executor.h 0 0
src/include/nodes/execnodes.h 4 0
src/include/nodes/parsenodes.h 1 0
src/include/nodes/plannodes.h 4 0
src/include/nodes/relation.h 4 0
src/include/optimizer/planmain.h 2 0
src/test/regress/expected/with.out 214 0
src/test/regress/sql/with.sql 132 0
*** a/doc/src/sgml/queries.sgml
--- b/doc/src/sgml/queries.sgml
***************
*** 1569,1574 **** GROUP BY region, product;
--- 1569,1595 ----
    </para>
  
    <para>
+    <literal>WITH</> clauses are not restricted to only <literal>SELECT</>
+    queries; you can also use <literal>INSERT</>, <literal>UPDATE</> or
+    <literal>DELETE</>.  This allows you to perform many different operations
+    in the same query.  An example of this is:
+ 
+ <programlisting>
+ WITH moved_rows AS (
+     DELETE FROM ONLY products
+     WHERE
+         "date" &gt;= '2009-10-01' AND
+         "date" &lt;  '2009-11-01
+     RETURNING *
+ )
+ INSERT INTO products_log
+ SELECT * FROM moved_rows;
+ </programlisting>
+ 
+    which moves rows from products to products_log.
+   </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/create_rule.sgml
--- b/doc/src/sgml/ref/create_rule.sgml
***************
*** 223,228 **** CREATE [ OR REPLACE ] RULE <replaceable class="parameter">name</replaceable> AS
--- 223,235 ----
     there is no <literal>RETURNING</> clause in any available rule.
    </para>
  
+    <para>
+    In an <literal>INSERT</literal>, <literal>UPDATE</literal> or
+    <literal>DELETE</literal> query within a <literal>WITH</literal> clause,
+    only unconditional, single-statement <literal>INSTEAD</literal> rules are
+    implemented.
+   </para>
+ 
    <para>
     It is very important to take care to avoid circular rules.  For
     example, though each of the following two rule definitions are
***************
*** 274,276 **** UPDATE mytable SET name = 'foo' WHERE id = 42;
--- 281,287 ----
    </para>
   </refsect1>
  </refentry>
+ <!--
+ doc/src/sgml/ref/create_rule.sgml
+ PostgreSQL documentation
+ -->
*** a/doc/src/sgml/ref/delete.sgml
--- b/doc/src/sgml/ref/delete.sgml
***************
*** 90,96 **** DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ]
       <para>
        The <literal>WITH</literal> clause allows you to specify one or more
        subqueries that can be referenced by name in the <command>DELETE</>
!       query. See <xref linkend="queries-with"> and <xref linkend="sql-select">
        for details.
       </para>
      </listitem>
--- 90,96 ----
       <para>
        The <literal>WITH</literal> clause allows you to specify one or more
        subqueries that can be referenced by name in the <command>DELETE</>
!       query. See <xref linkend="queries-with">
        for details.
       </para>
      </listitem>
*** 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> )
  
  TABLE { [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] | <replaceable class="parameter">with_query_name</replaceable> }
  </synopsis>
***************
*** 206,213 **** TABLE { [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] |
  
     <para>
      The <literal>WITH</literal> clause allows you to specify one or more
!     subqueries that can be referenced by name in the primary query.
!     The subqueries effectively act as temporary tables or views
      for the duration of the primary query.
     </para>
  
--- 206,213 ----
  
     <para>
      The <literal>WITH</literal> clause allows you to specify one or more
!     statements that can be referenced by name in the primary query.
!     The output of those statements effectively acts as temporary tables or views
      for the duration of the primary query.
     </para>
  
***************
*** 215,233 **** TABLE { [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] |
      A name (without schema qualification) must be specified for each
      <literal>WITH</literal> query.  Optionally, a list of column names
      can be specified; if this is omitted,
!     the column names are inferred from the subquery.
     </para>
  
     <para>
      If <literal>RECURSIVE</literal> is specified, it allows a
!     subquery to reference itself by name.  Such a subquery must have
      the form
  <synopsis>
  <replaceable class="parameter">non_recursive_term</replaceable> UNION [ ALL | DISTINCT ] <replaceable class="parameter">recursive_term</replaceable>
  </synopsis>
      where the recursive self-reference must appear on the right-hand
      side of the <literal>UNION</>.  Only one recursive self-reference
!     is permitted per query.
     </para>
  
     <para>
--- 215,266 ----
      A name (without schema qualification) must be specified for each
      <literal>WITH</literal> query.  Optionally, a list of column names
      can be specified; if this is omitted,
!     the column names are inferred from the statement.
     </para>
  
     <para>
+     You can also use <literal>INSERT</literal>, <literal>UPDATE</literal> and
+     <literal>DELETE</literal> in a <literal>WITH</literal> query.  These statements
+     are executed, in order, before the main query is executed and the results of
+     their respective <literal>RETURNING</literal> clauses are made available to the
+     main query.  If a statement doesn't have a <literal>RETURNING</literal> clause,
+     it is still executed normally, but referring to its <literal>WITH</literal> query
+     will result in an error.  You can mix <literal>SELECT</literal>,
+     <literal>INSERT</literal>, <literal>UPDATE</literal> and <literal>DELETE</literal>
+     statements in a single <literal>WITH</literal> list.  Only <literal>SELECT</literal>
+     queries are allowed below the top level.
+    </para>
+ 
+    <note>
+     <para>
+      Because <literal>INSERT</literal>, <literal>UPDATE</literal> and
+      <literal>DELETE</literal> statements within <literal>WITH</literal>
+      clauses need to be executed before the main query, their
+      <literal>RETURNING</literal> results need to be stored somewhere.  If those
+      rows don't fit into <varname>work_mem</varname>, they will be stored on disk
+      for the duration of the whole query.
+     </para>
+    </note>
+ 
+    <para>
+     In an <literal>INSERT</literal>, <literal>UPDATE</literal> or
+     <literal>DELETE</literal> query within a <literal>WITH</literal> statement,
+     only unconditional, single-statement <literal>INSTEAD</literal> rules are
+     implemented.
+     </para>
+ 
+    <para>
      If <literal>RECURSIVE</literal> is specified, it allows a
!     SELECT query to reference itself by name.  Such a subquery must have
      the form
  <synopsis>
  <replaceable class="parameter">non_recursive_term</replaceable> UNION [ ALL | DISTINCT ] <replaceable class="parameter">recursive_term</replaceable>
  </synopsis>
      where the recursive self-reference must appear on the right-hand
      side of the <literal>UNION</>.  Only one recursive self-reference
!     is permitted per query.  INSERT, UPDATE, and DELETE CTEs cannot be
!     recursive, although they may appear in statements containing
!     recursive SELECT CTEs.
     </para>
  
     <para>
***************
*** 237,243 **** TABLE { [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] |
      circular references, or mutual recursion, are not implemented.)
      Without <literal>RECURSIVE</literal>, <literal>WITH</literal> queries
      can only reference sibling <literal>WITH</literal> queries
!     that are earlier in the <literal>WITH</literal> list.
     </para>
  
     <para>
--- 270,278 ----
      circular references, or mutual recursion, are not implemented.)
      Without <literal>RECURSIVE</literal>, <literal>WITH</literal> queries
      can only reference sibling <literal>WITH</literal> queries
!     that are earlier in the <literal>WITH</literal> list.  For INSERT,
!     UPDATE and DELETE, the WITH queries are reordered to eliminate
!     these forward references.
     </para>
  
     <para>
*** a/doc/src/sgml/ref/update.sgml
--- b/doc/src/sgml/ref/update.sgml
***************
*** 86,92 **** UPDATE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <rep
       <para>
        The <literal>WITH</literal> clause allows you to specify one or more
        subqueries that can be referenced by name in the <command>UPDATE</>
!       query. See <xref linkend="queries-with"> and <xref linkend="sql-select">
        for details.
       </para>
      </listitem>
--- 86,92 ----
       <para>
        The <literal>WITH</literal> clause allows you to specify one or more
        subqueries that can be referenced by name in the <command>UPDATE</>
!       query. See <xref linkend="queries-with">
        for details.
       </para>
      </listitem>
*** 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->hasDmlWith)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("DML WITH is not allowed in a cursor declaration")));
+ 
  	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
***************
*** 973,979 **** ExecuteTruncate(TruncateStmt *stmt)
  		InitResultRelInfo(resultRelInfo,
  						  rel,
  						  0,	/* dummy rangetable index */
- 						  CMD_DELETE,	/* don't need any index info */
  						  0);
  		resultRelInfo++;
  	}
--- 973,978 ----
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
***************
*** 399,404 **** DefineView(ViewStmt *stmt, const char *queryString)
--- 399,405 ----
  	Query	   *viewParse;
  	Oid			viewOid;
  	RangeVar   *view;
+ 	ListCell   *lc;
  
  	/*
  	 * Run parse analysis to convert the raw parse tree to a Query.  Note this
***************
*** 417,422 **** DefineView(ViewStmt *stmt, const char *queryString)
--- 418,435 ----
  		viewParse->commandType != CMD_SELECT)
  		elog(ERROR, "unexpected parse analysis result");
  
+ 	/* .. but it doesn't check for DML WITH */
+ 	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("DML WITH is 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
***************
*** 161,169 **** standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
  	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;
  
--- 161,170 ----
  	switch (queryDesc->operation)
  	{
  		case CMD_SELECT:
! 			/* SELECT INTO, SELECT FOR UPDATE/SHARE and DML WITHs need to mark tuples */
  			if (queryDesc->plannedstmt->intoClause != NULL ||
! 				queryDesc->plannedstmt->rowMarks != NIL ||
! 				queryDesc->plannedstmt->hasDmlWith)
  				estate->es_output_cid = GetCurrentCommandId(true);
  			break;
  
***************
*** 681,687 **** InitPlan(QueryDesc *queryDesc, int eflags)
  			InitResultRelInfo(resultRelInfo,
  							  resultRelation,
  							  resultRelationIndex,
- 							  operation,
  							  estate->es_instrument);
  			resultRelInfo++;
  		}
--- 682,687 ----
***************
*** 812,817 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 812,872 ----
  	 */
  	planstate = ExecInitNode(plan, estate, eflags);
  
+ 	/* Add any DML WITH statements into estate */
+ 	if (plannedstmt->hasDmlWith)
+ 	{
+ 		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;
+ 
+ 			/*
+ 			 * Any CTE referenced in the query will have a "leader" CteScanState.  All
+ 			 * other CteScanStates associated with that CTE will use the leader's
+ 			 * tuplestore, so we only need to make sure the leader has all the rows from
+ 			 * the RETURNING.  The executor will make that happen as long as we add the
+ 			 * leader CteScanState to es_prescanstates.  Not having a leader means the
+ 			 * CTE is not referenced anywhere, so we just add the ModifyTable node and
+ 			 * the executor will ignore its output.  This avoids storing RETURNING
+ 			 * tuples for unreferenced CTEs.
+ 			 */
+ 
+ 			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 SELECT queries */
+ 				if (!IsA(ps, ModifyTableState))
+ 					continue;
+ 
+ 				estate->es_prescanstates = lappend(estate->es_prescanstates,
+ 												   leader);
+ 			}
+ 			else
+ 			{
+ 				ps = (PlanState *) list_nth(estate->es_subplanstates,
+ 											sp->plan_id - 1);
+ 
+ 				/* must be DML (see comment above) */
+ 				Assert(IsA(ps, ModifyTableState));
+ 
+ 				estate->es_prescanstates = lappend(estate->es_prescanstates,
+ 												   ps);
+ 			}
+ 		}
+ 	}
+ 
  	/*
  	 * Get the tuple descriptor describing the type of tuples to return. (this
  	 * is especially important if we are creating a relation with "SELECT
***************
*** 874,880 **** void
  InitResultRelInfo(ResultRelInfo *resultRelInfo,
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
- 				  CmdType operation,
  				  int instrument_options)
  {
  	TriggerDesc	*trigDesc = resultRelationDesc->trigdesc;
--- 929,934 ----
***************
*** 903,938 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
  							RelationGetRelationName(resultRelationDesc))));
  			break;
  		case RELKIND_VIEW:
! 			switch (operation)
! 			{
! 				case CMD_INSERT:
! 					if (!trigDesc || !trigDesc->trig_insert_instead_row)
! 						ereport(ERROR,
! 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! 								 errmsg("cannot insert into view \"%s\"",
! 										RelationGetRelationName(resultRelationDesc)),
! 								 errhint("You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.")));
! 					break;
! 				case CMD_UPDATE:
! 					if (!trigDesc || !trigDesc->trig_update_instead_row)
! 						ereport(ERROR,
! 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! 								 errmsg("cannot update view \"%s\"",
! 										RelationGetRelationName(resultRelationDesc)),
! 								 errhint("You need an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.")));
! 					break;
! 				case CMD_DELETE:
! 					if (!trigDesc || !trigDesc->trig_delete_instead_row)
! 						ereport(ERROR,
! 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! 								 errmsg("cannot delete from view \"%s\"",
! 										RelationGetRelationName(resultRelationDesc)),
! 								 errhint("You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.")));
! 					break;
! 				default:
! 					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
! 					break;
! 			}
  			break;
  		default:
  			ereport(ERROR,
--- 957,963 ----
  							RelationGetRelationName(resultRelationDesc))));
  			break;
  		case RELKIND_VIEW:
! 			/* checked in nodeModifyTable.c */
  			break;
  		default:
  			ereport(ERROR,
***************
*** 972,987 **** 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);
  }
  
  /*
--- 997,1002 ----
***************
*** 1037,1051 **** 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);
--- 1052,1064 ----
  
  	/*
  	 * 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);
***************
*** 1211,1216 **** ExecutePlan(EState *estate,
--- 1224,1230 ----
  {
  	TupleTableSlot *slot;
  	long		current_tuple_count;
+ 	ListCell	   *lc;
  
  	/*
  	 * initialize local variables
***************
*** 1223,1228 **** ExecutePlan(EState *estate,
--- 1237,1269 ----
  	estate->es_direction = direction;
  
  	/*
+ 	 * If there were any DML WITH statements, InitPlan() prepared a list of
+ 	 * statements to be ran before the top-level statement.
+ 	 */
+ 	foreach(lc, estate->es_prescanstates)
+ 	{
+ 		TupleTableSlot *slot;
+ 		PlanState *ps = (PlanState *) lfirst(lc);
+ 
+ 		for (;;)
+ 		{
+ 			slot = ExecProcNode(ps);
+ 			if (TupIsNull(slot))
+ 				break;
+ 		}
+ 
+ 		if (IsA(ps, CteScanState))
+ 		{
+ 			/*
+ 			 * We need to rewind CTEs because they might be used in the top-level
+ 			 * statement.  Otherwise the top-level statement would think that there
+ 			 * were no tuples because the CTE would return NULL.
+ 			 */
+ 			ExecReScan(ps);
+ 		}
+ 	}
+ 
+ 	/*
  	 * Loop until we've processed the proper number of tuples from the plan.
  	 */
  	for (;;)
***************
*** 2003,2009 **** 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)
--- 2044,2051 ----
  	 * 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 then.
  	 */
  	Assert(estate->es_subplanstates == NIL);
  	foreach(l, parentestate->es_plannedstmt->subplans)
***************
*** 2011,2017 **** 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);
--- 2053,2063 ----
  		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)
  {
***************
*** 281,287 **** ExecInsert(TupleTableSlot *slot,
  												   estate);
  	}
  
! 	(estate->es_processed)++;
  	estate->es_lastoid = newId;
  	setLastTid(&(tuple->t_self));
  
--- 282,290 ----
  												   estate);
  	}
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
! 
  	estate->es_lastoid = newId;
  	setLastTid(&(tuple->t_self));
  
***************
*** 313,319 **** ExecInsert(TupleTableSlot *slot,
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(ItemPointer tupleid,
  		   HeapTupleHeader oldtuple,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
--- 316,323 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(bool canSetTag,
! 		   ItemPointer tupleid,
  		   HeapTupleHeader oldtuple,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
***************
*** 427,433 **** ldelete:;
  		 */
  	}
  
! 	(estate->es_processed)++;
  
  	/* AFTER ROW DELETE Triggers */
  	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
--- 431,438 ----
  		 */
  	}
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
  
  	/* AFTER ROW DELETE Triggers */
  	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
***************
*** 496,502 **** ldelete:;
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(ItemPointer tupleid,
  		   HeapTupleHeader oldtuple,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
--- 501,508 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(bool canSetTag,
! 		   ItemPointer tupleid,
  		   HeapTupleHeader oldtuple,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
***************
*** 688,694 **** lreplace:;
  												   estate);
  	}
  
! 	(estate->es_processed)++;
  
  	/* AFTER ROW UPDATE Triggers */
  	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
--- 694,701 ----
  												   estate);
  	}
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
  
  	/* AFTER ROW UPDATE Triggers */
  	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
***************
*** 715,729 **** 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");
--- 722,736 ----
  	{
  		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");
***************
*** 741,755 **** 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");
--- 748,762 ----
  	{
  		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");
***************
*** 771,776 **** ExecModifyTable(ModifyTableState *node)
--- 778,784 ----
  	EState	   *estate = node->ps.state;
  	CmdType		operation = node->operation;
  	PlanState  *subplanstate;
+ 	ResultRelInfo *resultRelInfo;
  	JunkFilter *junkfilter;
  	TupleTableSlot *slot;
  	TupleTableSlot *planSlot;
***************
*** 797,803 **** ExecModifyTable(ModifyTableState *node)
  
  	/* 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
--- 805,812 ----
  
  	/* 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
***************
*** 821,829 **** 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;
  			}
--- 830,838 ----
  			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;
  			}
***************
*** 844,850 **** ExecModifyTable(ModifyTableState *node)
  				Datum		datum;
  				bool		isNull;
  
! 				if (estate->es_result_relation_info->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
  				{
  					datum = ExecGetJunkAttribute(slot,
  												 junkfilter->jf_junkAttNo,
--- 853,859 ----
  				Datum		datum;
  				bool		isNull;
  
! 				if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
  				{
  					datum = ExecGetJunkAttribute(slot,
  												 junkfilter->jf_junkAttNo,
***************
*** 877,893 **** ExecModifyTable(ModifyTableState *node)
  				slot = ExecFilterJunk(junkfilter, slot);
  		}
  
  		switch (operation)
  		{
  			case CMD_INSERT:
! 				slot = ExecInsert(slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(tupleid, oldtuple, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
--- 886,909 ----
  				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, oldtuple, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(node->canSetTag, tupleid, oldtuple, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
***************
*** 955,979 **** 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;
--- 971,1051 ----
  	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)
  	{
+ 		Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
  		subplan = (Plan *) lfirst(l);
+ 
+ 		/*
+ 		 * If the result relation is a view, check that it has the correct
+ 		 * trigger.
+ 		 */
+ 		if (resultRelationDesc->rd_rel->relkind == RELKIND_VIEW)
+ 		{
+ 			TriggerDesc *trigDesc = resultRelationDesc->trigdesc;
+ 
+ 			switch (operation)
+ 			{
+ 				case CMD_INSERT:
+ 					if (!trigDesc || !trigDesc->trig_insert_instead_row)
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 								 errmsg("cannot insert into view \"%s\"",
+ 										RelationGetRelationName(resultRelationDesc)),
+ 								 errhint("You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.")));
+ 					break;
+ 				case CMD_UPDATE:
+ 					if (!trigDesc || !trigDesc->trig_update_instead_row)
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 								 errmsg("cannot update view \"%s\"",
+ 										RelationGetRelationName(resultRelationDesc)),
+ 								 errhint("You need an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.")));
+ 					break;
+ 				case CMD_DELETE:
+ 					if (!trigDesc || !trigDesc->trig_delete_instead_row)
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 								 errmsg("cannot delete from view \"%s\"",
+ 										RelationGetRelationName(resultRelationDesc)),
+ 								 errhint("You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.")));
+ 					break;
+ 				default:
+ 					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+ 					break;
+ 			}
+ 		}
+ 
+ 		/*
+ 		 * 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;
***************
*** 1010,1017 **** 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);
--- 1082,1088 ----
  		/*
  		 * Build a projection for each result rel.
  		 */
! 		resultRelInfo = mtstate->resultRelInfo;
  		foreach(l, node->returningLists)
  		{
  			List	   *rlist = (List *) lfirst(l);
***************
*** 1110,1116 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  
  		if (junk_filter_needed)
  		{
! 			resultRelInfo = estate->es_result_relations;
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
--- 1181,1187 ----
  
  		if (junk_filter_needed)
  		{
! 			resultRelInfo = mtstate->resultRelInfo;
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
***************
*** 1148,1154 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  		else
  		{
  			if (operation == CMD_INSERT)
! 				ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc,
  									subplan->targetlist);
  		}
  	}
--- 1219,1225 ----
  		else
  		{
  			if (operation == CMD_INSERT)
! 				ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
  									subplan->targetlist);
  		}
  	}
*** a/src/backend/executor/nodeSubplan.c
--- b/src/backend/executor/nodeSubplan.c
***************
*** 660,665 **** ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
--- 660,667 ----
  	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
***************
*** 86,91 **** _copyPlannedStmt(PlannedStmt *from)
--- 86,92 ----
  	COPY_NODE_FIELD(resultRelations);
  	COPY_NODE_FIELD(utilityStmt);
  	COPY_NODE_FIELD(intoClause);
+ 	COPY_SCALAR_FIELD(hasDmlWith);
  	COPY_NODE_FIELD(subplans);
  	COPY_BITMAPSET_FIELD(rewindPlanIDs);
  	COPY_NODE_FIELD(rowMarks);
***************
*** 173,179 **** _copyModifyTable(ModifyTable *from)
--- 174,182 ----
  	 * 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);
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 2400,2405 **** bool
--- 2400,2455 ----
  					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
***************
*** 328,333 **** _outModifyTable(StringInfo str, ModifyTable *node)
--- 328,334 ----
  
  	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);
***************
*** 1580,1585 **** _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
--- 1581,1587 ----
  	WRITE_NODE_FIELD(finalrowmarks);
  	WRITE_NODE_FIELD(relationOids);
  	WRITE_NODE_FIELD(invalItems);
+ 	WRITE_NODE_FIELD(resultRelations);
  	WRITE_UINT_FIELD(lastPHId);
  	WRITE_BOOL_FIELD(transientPlan);
  }
***************
*** 1595,1601 **** _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);
--- 1597,1602 ----
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 4164,4170 **** 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)
  {
--- 4164,4171 ----
   * to make it look better sometime.
   */
  ModifyTable *
! make_modifytable(CmdType operation, bool canSetTag,
! 				 List *resultRelations,
  				 List *subplans, List *returningLists,
  				 List *rowMarks, int epqParam)
  {
***************
*** 4173,4179 **** 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));
  
--- 4174,4179 ----
***************
*** 4214,4219 **** make_modifytable(CmdType operation, List *resultRelations,
--- 4214,4220 ----
  		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
***************
*** 166,171 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
--- 166,173 ----
  	glob->finalrowmarks = NIL;
  	glob->relationOids = NIL;
  	glob->invalItems = NIL;
+ 	glob->hasDmlWith = false;
+ 	glob->resultRelations = NIL;
  	glob->lastPHId = 0;
  	glob->transientPlan = false;
  
***************
*** 243,251 **** 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;
--- 245,254 ----
  	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->hasDmlWith = glob->hasDmlWith;
  	result->subplans = glob->subplans;
  	result->rewindPlanIDs = glob->rewindPlanIDs;
  	result->rowMarks = glob->finalrowmarks;
***************
*** 571,577 **** subquery_planner(PlannerGlobal *glob, Query *parse,
  				rowMarks = root->rowMarks;
  
  			plan = (Plan *) make_modifytable(parse->commandType,
! 										   copyObject(root->resultRelations),
  											 list_make1(plan),
  											 returningLists,
  											 rowMarks,
--- 574,581 ----
  				rowMarks = root->rowMarks;
  
  			plan = (Plan *) make_modifytable(parse->commandType,
! 											 parse->canSetTag,
! 											 list_make1_int(parse->resultRelation),
  											 list_make1(plan),
  											 returningLists,
  											 rowMarks,
***************
*** 848,855 **** inheritance_planner(PlannerInfo *root)
  	/* Done with child context */
  	MemoryContextDelete(childcxt);
  
- 	root->resultRelations = resultRelations;
- 
  	/* Mark result as unordered (probably unnecessary) */
  	root->query_pathkeys = NIL;
  
--- 852,857 ----
***************
*** 859,865 **** 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,
--- 861,866 ----
***************
*** 886,892 **** 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,
--- 887,894 ----
  
  	/* 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,
***************
*** 1762,1773 **** 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.
--- 1764,1769 ----
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 538,543 **** set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
--- 538,547 ----
  											  (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
***************
*** 928,944 **** 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
  		 * this similar to make_subplan.
--- 928,960 ----
  		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;
  		}
  
+ 		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("DML WITH without RETURNING is only allowed inside an unreferenced CTE")));
+ 
+ 			/* only allowed at the top level */
+ 			Assert(root->query_level == 1);
+ 
+ 			root->glob->hasDmlWith = true;
+ 		}
+ 
  		/*
  		 * Copy the source Query node.	Probably not necessary, but let's keep
  		 * this similar to make_subplan.
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 7852,7863 **** cte_list:
  		| cte_list ',' common_table_expr		{ $$ = lappend($1, $3); }
  		;
  
! common_table_expr:  name opt_name_list AS select_with_parens
  			{
  				CommonTableExpr *n = makeNode(CommonTableExpr);
  				n->ctename = $1;
  				n->aliascolnames = $2;
! 				n->ctequery = $4;
  				n->location = @1;
  				$$ = (Node *) n;
  			}
--- 7852,7863 ----
  		| cte_list ',' common_table_expr		{ $$ = lappend($1, $3); }
  		;
  
! common_table_expr:  name opt_name_list AS '(' PreparableStmt ')'
  			{
  				CommonTableExpr *n = makeNode(CommonTableExpr);
  				n->ctename = $1;
  				n->aliascolnames = $2;
! 				n->ctequery = $5;
  				n->location = @1;
  				$$ = (Node *) n;
  			}
*** 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,241 ----
  analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
  {
  	Query	   *query;
! 	List	   *cteList;
  
  	query = parse_sub_analyze(cte->ctequery, pstate, cte, false);
  	cte->ctequery = (Node *) query;
  
! 	/* Check that we got something reasonable. */
! 	if (!IsA(query, Query))
! 		elog(ERROR, "unexpected non-Query as subquery in WITH");
! 	if (query->utilityStmt != NULL)
! 		elog(ERROR, "unexpected utility statement in subquery in WITH");
  	if (query->intoClause)
  		ereport(ERROR,
  				(errcode(ERRCODE_SYNTAX_ERROR),
***************
*** 248,257 **** analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
  				 parser_errposition(pstate,
  								 exprLocation((Node *) query->intoClause))));
  
  	if (!cte->cterecursive)
  	{
  		/* Compute the output column names/types if not done yet */
! 		analyzeCTETargetList(pstate, cte, query->targetList);
  	}
  	else
  	{
--- 243,257 ----
  				 parser_errposition(pstate,
  								 exprLocation((Node *) query->intoClause))));
  
+ 	if (query->commandType == CMD_SELECT)
+ 		cteList = query->targetList;
+ 	else
+ 		cteList = query->returningList;
+ 
  	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;
--- 269,275 ----
  		lctyp = list_head(cte->ctecoltypes);
  		lctypmod = list_head(cte->ctecoltypmods);
  		varattno = 0;
! 		foreach(lctlist, cteList)
  		{
  			TargetEntry *te = (TargetEntry *) lfirst(lctlist);
  			Node	   *texpr;
***************
*** 299,304 **** analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
--- 299,312 ----
  		if (lctyp != NULL || lctypmod != NULL)	/* shouldn't happen */
  			elog(ERROR, "wrong number of output columns in WITH");
  	}
+ 
+ 	/* Currently we can't deal with non-top-level DML WITHs */
+ 	if (pstate->parentParseState != NULL &&
+ 		query->commandType != CMD_SELECT)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg("DML WITH is only allowed at the top level"),
+ 				 	parser_errposition(pstate, cte->location)));
  }
  
  /*
***************
*** 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,
--- 603,621 ----
  		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, Query)); /* not analyzed yet */
+ 
+ 		/* Must be a SELECT statement */
+ 		if (!IsA(stmt, SelectStmt))
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 					 errmsg("Recursive DML WITH statements are not supported"),
+ 					 parser_errposition(cstate->pstate, cte->location)));
+ 
  		/* Must have top-level UNION */
  		if (stmt->op != SETOP_UNION)
  			ereport(ERROR,
*** a/src/backend/parser/parse_target.c
--- b/src/backend/parser/parse_target.c
***************
*** 324,333 **** 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",
--- 324,343 ----
  			{
  				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",
***************
*** 1409,1419 **** 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);
--- 1419,1438 ----
  			{
  				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);
***************
*** 1436,1442 **** 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);
--- 1455,1461 ----
  						 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
***************
*** 1788,1793 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1788,1797 ----
  	bool		returning = false;
  	Query	   *qual_product = NULL;
  	List	   *rewritten = NIL;
+ 	ListCell	*lc1;
+ 	CommonTableExpr	*cte;
+ 	Query		*ctequery;
+ 	List		*newstuff;
  
  	/*
  	 * If the statement is an insert, update, or delete, adjust its targetlist
***************
*** 1913,1919 **** 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);
--- 1917,1922 ----
***************
*** 1968,1973 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1971,2041 ----
  	}
  
  	/*
+ 	 * Rewrite DML WITH statements.
+ 	 */
+ 	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, single-statement 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 are not supported in DML WITH statements")));
+ 				if (qsrc == QSRC_NON_INSTEAD_RULE)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("DO ALSO rules are not supported in DML WITH statements")));
+ 				if (qsrc == QSRC_INSTEAD_RULE)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("Multi-statement DO INSTEAD rules are not 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 are not supported in DML WITH statements")));
+ 		}
+ 		else
+ 		{
+ 			Assert(list_length(newstuff) == 1);
+ 
+ 			ctequery = linitial(newstuff);
+ 
+ 			/*
+ 			 * To get the correct amount of affected rows, we want only the top-level
+ 			 * statement to increment the counter.  ModifyTable nodes use canSetTag
+ 			 * to determine whether they're top-level or not.
+ 			 */
+ 			ctequery->canSetTag = false;
+ 
+ 			cte->ctequery = (Node *) ctequery;
+ 		}
+ 	}
+ 
+ 	/*
  	 * 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
***************
*** 295,300 **** ChoosePortalStrategy(List *stmts)
--- 295,301 ----
  			if (pstmt->canSetTag)
  			{
  				if (pstmt->commandType == CMD_SELECT &&
+ 					!pstmt->hasDmlWith &&
  					pstmt->utilityStmt == NULL &&
  					pstmt->intoClause == NULL)
  					return PORTAL_ONE_SELECT;
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 121,126 **** CommandIsReadOnly(Node *parsetree)
--- 121,128 ----
  					return false;		/* SELECT INTO */
  				else if (stmt->rowMarks != NIL)
  					return false;		/* SELECT FOR UPDATE/SHARE */
+ 				else if (stmt->hasDmlWith)
+ 					return false;		/* DML WITHs */
  				else
  					return true;
  			case CMD_UPDATE:
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 4116,4124 **** 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",
--- 4116,4131 ----
  				}
  				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
***************
*** 165,171 **** extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
  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);
--- 165,170 ----
*** 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	   *es_prescanstates; /* List of PlanStates to be scanned before the main plan */
  	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
***************
*** 874,880 **** typedef struct CommonTableExpr
  	NodeTag		type;
  	char	   *ctename;		/* query name (never qualified) */
  	List	   *aliascolnames;	/* optional list of column names */
! 	Node	   *ctequery;		/* subquery (SelectStmt or Query) */
  	int			location;		/* token location, or -1 if unknown */
  	/* These fields are set during parse analysis: */
  	bool		cterecursive;	/* is this CTE actually recursive? */
--- 874,880 ----
  	NodeTag		type;
  	char	   *ctename;		/* query name (never qualified) */
  	List	   *aliascolnames;	/* optional list of column names */
! 	Node	   *ctequery;		/* subquery (Stmt or Query) */
  	int			location;		/* token location, or -1 if unknown */
  	/* These fields are set during parse analysis: */
  	bool		cterecursive;	/* is this CTE actually recursive? */
*** 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		hasDmlWith;		/* are there any DML WITH statements? */
+ 
  	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		hasDmlWith;		/* are there any DML WITH statements? */
+ 
+ 	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
***************
*** 78,85 **** 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);
  
--- 78,85 ----
  		   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/test/regress/expected/with.out
--- b/src/test/regress/expected/with.out
***************
*** 1159,1161 **** SELECT * FROM t;
--- 1159,1375 ----
   10
  (55 rows)
  
+ --
+ -- DML WITH
+ --
+ -- INSERT .. 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)
+ 
+ -- UPDATE .. RETURNING
+ 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)
+ 
+ -- DELETE .. RETURNING
+ 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)
+ 
+ -- forward reference
+ WITH RECURSIVE t AS (
+ 	INSERT INTO y
+ 		SELECT a+5 FROM t2 WHERE a > 5
+ 	RETURNING *
+ ), t2 AS (
+ 	UPDATE y SET a=a-11 RETURNING *
+ )
+ SELECT * FROM t
+ UNION ALL
+ SELECT * FROM t2;
+  a  
+ ----
+  11
+  12
+  13
+  14
+  15
+   0
+   1
+   2
+   3
+   4
+   5
+   6
+   7
+   8
+   9
+  10
+ (16 rows)
+ 
+ -- unconditional DO INSTEAD rule
+ CREATE RULE y_rule AS ON DELETE TO y DO INSTEAD INSERT INTO y VALUES(0) RETURNING *;
+ WITH t AS (
+ 	DELETE FROM y RETURNING *
+ )
+ SELECT * FROM t;
+  a 
+ ---
+  0
+ (1 row)
+ 
+ DROP RULE y_rule ON y;
+ -- a truly recursive CTE in the same list
+ WITH RECURSIVE t(a) AS (
+ 	SELECT 0
+ 		UNION ALL
+ 	SELECT a+1 FROM t WHERE a+1 < 5
+ ), t2 as (
+ 	INSERT INTO y
+ 		SELECT * FROM t RETURNING *
+ )
+ SELECT * FROM t2 JOIN y USING (a) ORDER BY a;
+  a 
+ ---
+  0
+  0
+  1
+  2
+  3
+  4
+ (6 rows)
+ 
+ -- error cases
+ -- DML WITH tries to use its own output
+ WITH RECURSIVE t AS (
+ 	INSERT INTO y
+ 		SELECT * FROM t
+ )
+ VALUES(FALSE);
+ ERROR:  Recursive DML WITH statements are not supported
+ LINE 1: WITH RECURSIVE t AS (
+                        ^
+ -- no RETURNING in a references DML WITH
+ WITH t AS (
+ 	INSERT INTO y VALUES(0)
+ )
+ SELECT * FROM t;
+ ERROR:  DML WITH without RETURNING is only allowed inside an unreferenced CTE
+ -- DML WITH only at the top level
+ SELECT * FROM (
+ 	WITH t AS (UPDATE y SET a=a+1 RETURNING *)
+ 	SELECT * FROM t
+ ) ss;
+ ERROR:  DML WITH is only allowed at the top level
+ LINE 2:  WITH t AS (UPDATE y SET a=a+1 RETURNING *)
+               ^
+ -- no DML WITH in VIEWs
+ CREATE VIEW yv AS
+ 	WITH t AS (UPDATE y SET a=a+1 RETURNING *)
+ 	SELECT * FROM t;
+ ERROR:  DML WITH is not allowed in a view definition
+ -- no DML WITH in CURSORs
+ DECLARE f CURSOR FOR
+ 	WITH t AS (UPDATE y SET a=a+1 RETURNING *)
+ 	SELECT * FROM t;
+ ERROR:  DML WITH is not allowed in a cursor declaration
+ -- no DO INSTEAD NOTHING rules
+ CREATE RULE y_rule AS ON INSERT TO y DO INSTEAD NOTHING;
+ WITH t AS (
+ 	INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ ERROR:  DO INSTEAD NOTHING rules are not supported in DML WITH statements
+ DROP RULE y_rule ON y;
+ -- no DO ALSO rules
+ CREATE RULE y_rule AS ON INSERT TO y DO ALSO DELETE FROM y;
+ WITH t AS (
+ 	INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ ERROR:  DO ALSO rules are not supported in DML WITH statements
+ DROP RULE y_rule ON y;
+ -- no conditional DO INSTEAD rules
+ CREATE RULE y_rule AS ON INSERT TO y WHERE a=0 DO INSTEAD DELETE FROM y;
+ WITH t AS (
+ 	INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ ERROR:  Conditional DO INSTEAD rules are not supported in DML WITH statements
+ DROP RULE y_rule ON y;
+ -- no multi-statement DO INSTEAD rules
+ CREATE RULE y_rule AS ON INSERT TO y DO INSTEAD (UPDATE y SET a=a+1; DELETE FROM y);
+ WITH t AS (
+ 	INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ ERROR:  Multi-statement DO INSTEAD rules are not supported in DML WITH statements
+ DROP RULE y_rule ON y;
*** a/src/test/regress/sql/with.sql
--- b/src/test/regress/sql/with.sql
***************
*** 538,540 **** WITH RECURSIVE t(j) AS (
--- 538,672 ----
      SELECT j+1 FROM t WHERE j < 10
  )
  SELECT * FROM t;
+ 
+ --
+ -- DML WITH
+ --
+ 
+ -- INSERT .. RETURNING
+ WITH t AS (
+     INSERT INTO y
+     VALUES
+         (11),
+         (12),
+         (13),
+         (14),
+         (15),
+         (16),
+         (17),
+         (18),
+         (19),
+         (20)
+     RETURNING *
+ )
+ SELECT * FROM t;
+ 
+ -- UPDATE .. RETURNING
+ WITH t AS (
+     UPDATE y
+     SET a=a+1
+     RETURNING *
+ )
+ SELECT * FROM t;
+ 
+ -- DELETE .. RETURNING
+ WITH t AS (
+     DELETE FROM y
+     WHERE a <= 10
+     RETURNING *
+ )
+ SELECT * FROM t;
+ 
+ -- forward reference
+ WITH RECURSIVE t AS (
+ 	INSERT INTO y
+ 		SELECT a+5 FROM t2 WHERE a > 5
+ 	RETURNING *
+ ), t2 AS (
+ 	UPDATE y SET a=a-11 RETURNING *
+ )
+ SELECT * FROM t
+ UNION ALL
+ SELECT * FROM t2;
+ 
+ -- unconditional DO INSTEAD rule
+ CREATE RULE y_rule AS ON DELETE TO y DO INSTEAD INSERT INTO y VALUES(0) RETURNING *;
+ WITH t AS (
+ 	DELETE FROM y RETURNING *
+ )
+ SELECT * FROM t;
+ DROP RULE y_rule ON y;
+ 
+ -- a truly recursive CTE in the same list
+ WITH RECURSIVE t(a) AS (
+ 	SELECT 0
+ 		UNION ALL
+ 	SELECT a+1 FROM t WHERE a+1 < 5
+ ), t2 as (
+ 	INSERT INTO y
+ 		SELECT * FROM t RETURNING *
+ )
+ SELECT * FROM t2 JOIN y USING (a) ORDER BY a;
+ 
+ -- error cases
+ 
+ -- DML WITH tries to use its own output
+ WITH RECURSIVE t AS (
+ 	INSERT INTO y
+ 		SELECT * FROM t
+ )
+ VALUES(FALSE);
+ 
+ -- no RETURNING in a references DML WITH
+ WITH t AS (
+ 	INSERT INTO y VALUES(0)
+ )
+ SELECT * FROM t;
+ 
+ -- DML WITH only at the top level
+ SELECT * FROM (
+ 	WITH t AS (UPDATE y SET a=a+1 RETURNING *)
+ 	SELECT * FROM t
+ ) ss;
+ 
+ -- no DML WITH in VIEWs
+ CREATE VIEW yv AS
+ 	WITH t AS (UPDATE y SET a=a+1 RETURNING *)
+ 	SELECT * FROM t;
+ 
+ -- no DML WITH in CURSORs
+ DECLARE f CURSOR FOR
+ 	WITH t AS (UPDATE y SET a=a+1 RETURNING *)
+ 	SELECT * FROM t;
+ 
+ -- no DO INSTEAD NOTHING rules
+ CREATE RULE y_rule AS ON INSERT TO y DO INSTEAD NOTHING;
+ WITH t AS (
+ 	INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ DROP RULE y_rule ON y;
+ 
+ -- no DO ALSO rules
+ CREATE RULE y_rule AS ON INSERT TO y DO ALSO DELETE FROM y;
+ WITH t AS (
+ 	INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ DROP RULE y_rule ON y;
+ 
+ -- no conditional DO INSTEAD rules
+ CREATE RULE y_rule AS ON INSERT TO y WHERE a=0 DO INSTEAD DELETE FROM y;
+ WITH t AS (
+ 	INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ DROP RULE y_rule ON y;
+ 
+ -- no multi-statement DO INSTEAD rules
+ CREATE RULE y_rule AS ON INSERT TO y DO INSTEAD (UPDATE y SET a=a+1; DELETE FROM y);
+ WITH t AS (
+ 	INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ DROP RULE y_rule ON y;