Writeable CTEs documentation patch

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

Hi,

Attached is a documentation patch for writeable CTEs.

Most of it is explaining how this feature works in select.sgml. I
wasn't sure if that's the right place, but couldn't find a better one.
I also wasn't able to find any place discussing the command tag, other
than libpq's documentation. Is there one somewhere?

While working on the docs, I noticed one problem with the patch itself:
it doesn't handle multi-statement DO INSTEAD rules correctly. I'm going
to submit a fix for that later.

Any suggestions, whatsoever, are welcome.

Regards,
Marko Tiikkaja

Attachments:

dmlwith_doc1.pathtext/plain; charset=iso-8859-1; name=dmlwith_doc1.pathDownload
*** a/doc/src/sgml/queries.sgml
--- b/doc/src/sgml/queries.sgml
***************
*** 1530,1538 **** SELECT <replaceable>select_list</replaceable> FROM <replaceable>table_expression
  
    <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 (
--- 1530,1538 ----
  
    <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;
--- 1560,1586 ----
    </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 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 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
***************
*** 222,227 **** CREATE [ OR REPLACE ] RULE <replaceable class="parameter">name</replaceable> AS
--- 222,234 ----
    </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
     accepted by <productname>PostgreSQL</productname>, the
*** a/doc/src/sgml/ref/delete.sgml
--- b/doc/src/sgml/ref/delete.sgml
***************
*** 21,30 **** PostgreSQL documentation
--- 21,36 ----
  
   <refsynopsisdiv>
  <synopsis>
+ [ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
  DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
      [ USING <replaceable class="PARAMETER">using_list</replaceable> ]
      [ WHERE <replaceable class="PARAMETER">condition</replaceable> | WHERE CURRENT OF <replaceable class="PARAMETER">cursor_name</replaceable> ]
      [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+ 
+ <phrase>where <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> ) 
+ 
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 84,89 **** DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ]
--- 90,104 ----
  
    <variablelist>
     <varlistentry>
+     <term><replaceable class="PARAMETER">with_query</replaceable></term>
+     <listitem>
+      <para>
+       For information about with_query, see
+       <xref linkend="sql-with" endterm="sql-with-title">.
+      </para>
+     </listitem>
+    </varlistentry>
+    <varlistentry>
      <term><literal>ONLY</></term>
      <listitem>
       <para>
*** a/doc/src/sgml/ref/insert.sgml
--- b/doc/src/sgml/ref/insert.sgml
***************
*** 21,29 **** PostgreSQL documentation
--- 21,36 ----
  
   <refsynopsisdiv>
  <synopsis>
+ [ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
  INSERT INTO <replaceable class="PARAMETER">table</replaceable> [ ( <replaceable class="PARAMETER">column</replaceable> [, ...] ) ]
      { DEFAULT VALUES | VALUES ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) [, ...] | <replaceable class="PARAMETER">query</replaceable> }
      [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+ 
+ <phrase>where <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> )   
+ 
+ 
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 85,90 **** INSERT INTO <replaceable class="PARAMETER">table</replaceable> [ ( <replaceable
--- 92,106 ----
  
    <variablelist>
     <varlistentry>
+     <term><replaceable class="PARAMETER">with_query</replaceable></term>
+     <listitem>
+      <para>
+       For information about with_query, see
+       <xref linkend="sql-with" endterm="sql-with-title">.
+      </para>
+     </listitem>
+    </varlistentry>
+    <varlistentry>
      <term><replaceable class="PARAMETER">table</replaceable></term>
      <listitem>
       <para>
*** 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>
***************
*** 202,209 **** 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>
  
--- 202,209 ----
  
     <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 act as temporary tables or views
      for the duration of the primary query.
     </para>
  
***************
*** 211,229 **** 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 ] <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>
--- 211,261 ----
      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 statement must have
      the form
  <synopsis>
  <replaceable class="parameter">non_recursive_term</replaceable> UNION [ ALL ] <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 are not permitted in
!     a recursive query.
     </para>
  
     <para>
***************
*** 233,239 **** 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>
--- 265,273 ----
      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 and executed in that order.
     </para>
  
     <para>
*** a/doc/src/sgml/ref/update.sgml
--- b/doc/src/sgml/ref/update.sgml
***************
*** 21,32 **** PostgreSQL documentation
--- 21,38 ----
  
   <refsynopsisdiv>
  <synopsis>
+ [ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
  UPDATE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
      SET { <replaceable class="PARAMETER">column</replaceable> = { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } |
            ( <replaceable class="PARAMETER">column</replaceable> [, ...] ) = ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) } [, ...]
      [ FROM <replaceable class="PARAMETER">from_list</replaceable> ]
      [ WHERE <replaceable class="PARAMETER">condition</replaceable> | WHERE CURRENT OF <replaceable class="PARAMETER">cursor_name</replaceable> ]
      [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+ 
+ <phrase>where <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> )  
+ 
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 80,85 **** UPDATE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <rep
--- 86,100 ----
  
    <variablelist>
     <varlistentry>
+     <term><replaceable class="PARAMETER">with_query</replaceable></term>
+     <listitem>
+      <para>
+       For information about with_query, see
+       <xref linkend="sql-with" endterm="sql-with-title">.
+      </para>
+     </listitem>
+    </varlistentry>
+    <varlistentry>
      <term><replaceable class="PARAMETER">table</replaceable></term>
      <listitem>
       <para>
#2Marko Tiikkaja
marko.tiikkaja@cs.helsinki.fi
In reply to: Marko Tiikkaja (#1)
1 attachment(s)
Writeable CTEs patch

Hi,

On 2010-02-04 18:04 UTC+2, I wrote:

While working on the docs, I noticed one problem with the patch itself:
it doesn't handle multi-statement DO INSTEAD rules correctly. I'm going
to submit a fix for that later.

Here's an updated patch. Only changes from the previous patch are
fixing the above issue and a regression test for it.

Regards,
Marko Tiikkaja

Attachments:

dmlwith9.patchtext/plain; charset=iso-8859-1; name=dmlwith9.patchDownload
*** 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
***************
*** 940,946 **** ExecuteTruncate(TruncateStmt *stmt)
  		InitResultRelInfo(resultRelInfo,
  						  rel,
  						  0,	/* dummy rangetable index */
- 						  CMD_DELETE,	/* don't need any index info */
  						  0);
  		resultRelInfo++;
  	}
--- 940,945 ----
*** 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 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
***************
*** 72,77 **** static void ExecutePlan(EState *estate, PlanState *planstate,
--- 72,78 ----
  			DestReceiver *dest);
  static void ExecCheckRTPerms(List *rangeTable);
  static void ExecCheckRTEPerms(RangeTblEntry *rte);
+ static bool ExecTopLevelStmtIsReadOnly(PlannedStmt *stmt);
  static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
  static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
  							  Plan *planTree);
***************
*** 152,184 **** standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
  			palloc0(queryDesc->plannedstmt->nParamExec * sizeof(ParamExecData));
  
  	/*
! 	 * If non-read-only query, set the command ID to mark output tuples with
  	 */
! 	switch (queryDesc->operation)
  	{
! 		case CMD_SELECT:
! 			/* SELECT INTO and SELECT FOR UPDATE/SHARE need to mark tuples */
! 			if (queryDesc->plannedstmt->intoClause != NULL ||
! 				queryDesc->plannedstmt->rowMarks != NIL)
! 				estate->es_output_cid = GetCurrentCommandId(true);
! 			break;
! 
! 		case CMD_INSERT:
! 		case CMD_DELETE:
! 		case CMD_UPDATE:
! 			estate->es_output_cid = GetCurrentCommandId(true);
! 			break;
! 
! 		default:
! 			elog(ERROR, "unrecognized operation code: %d",
! 				 (int) queryDesc->operation);
! 			break;
  	}
  
- 	/*
- 	 * Copy other important information into the EState
- 	 */
- 	estate->es_snapshot = RegisterSnapshot(queryDesc->snapshot);
  	estate->es_crosscheck_snapshot = RegisterSnapshot(queryDesc->crosscheck_snapshot);
  	estate->es_instrument = queryDesc->instrument_options;
  
--- 153,181 ----
  			palloc0(queryDesc->plannedstmt->nParamExec * sizeof(ParamExecData));
  
  	/*
! 	 * To provide sane and predictable behaviour for DML WITH statements, we need
! 	 * to increase the CID and update the snapshot to reflect that after every
! 	 * statement.  This allows a statement to see a previously ran statement's
! 	 * tuples.  We don't want the executor to be scribbling on its input so we
! 	 * modify a copy of the snapshot.  Also we always use at least one CID.
  	 */
! 	if (queryDesc->plannedstmt->hasDmlWith)
  	{
! 		estate->es_output_cid = GetCurrentCommandId(true);
! 		estate->es_snapshot = RegisterSnapshotCopy(queryDesc->snapshot);
! 	}
! 	else
! 	{
! 		/*
! 		 * If there are no DML WITH statements, the top-level statement tells us
! 		 * whether or not we need to use a CID.  We can also use the provided
! 		 * snapshot since we're not going to modify it.
! 		 */
! 		estate->es_output_cid = GetCurrentCommandId(!ExecTopLevelStmtIsReadOnly
! 														(queryDesc->plannedstmt));
! 		estate->es_snapshot = RegisterSnapshot(queryDesc->snapshot);
  	}
  
  	estate->es_crosscheck_snapshot = RegisterSnapshot(queryDesc->crosscheck_snapshot);
  	estate->es_instrument = queryDesc->instrument_options;
  
***************
*** 566,571 **** ExecCheckRTEPerms(RangeTblEntry *rte)
--- 563,587 ----
  }
  
  /*
+  * Is the top-level statement read-only?
+  *
+  * Even if this function returns true, the statement might still contain
+  * INSERT, UPDATE or DELETE statements within a CTE; we only check the
+  * top-level statement.
+  */
+ static bool
+ ExecTopLevelStmtIsReadOnly(PlannedStmt *stmt)
+ {
+ 	if (stmt->commandType == CMD_SELECT &&
+ 		stmt->intoClause == NULL &&
+ 		stmt->rowMarks == NULL)
+ 		return true;
+ 	else
+ 		return false;
+ }
+ 
+ 
+ /*
   * Check that the query does not imply any writes to non-temp tables.
   */
  static void
***************
*** 665,671 **** InitPlan(QueryDesc *queryDesc, int eflags)
  			InitResultRelInfo(resultRelInfo,
  							  resultRelation,
  							  resultRelationIndex,
- 							  operation,
  							  estate->es_instrument);
  			resultRelInfo++;
  		}
--- 681,686 ----
***************
*** 796,801 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 811,877 ----
  	 */
  	planstate = ExecInitNode(plan, estate, eflags);
  
+ 	/* Add any DML WITH statements into estate */
+ 	estate->es_prescanstates = NIL;
+ 
+ 	if (plannedstmt->hasDmlWith)
+ 	{
+ 		/*
+ 		 * We need to know the "leader" CteScanState (see comment below) so we need
+ 		 * to look in initPlan.
+ 		 */
+ 		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 CTEs */
+ 				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
***************
*** 858,864 **** void
  InitResultRelInfo(ResultRelInfo *resultRelInfo,
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
- 				  CmdType operation,
  				  int instrument_options)
  {
  	/*
--- 934,939 ----
***************
*** 926,941 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
  	resultRelInfo->ri_ConstraintExprs = NULL;
  	resultRelInfo->ri_junkFilter = NULL;
  	resultRelInfo->ri_projectReturning = NULL;
- 
- 	/*
- 	 * If there are indices on the result relation, open them and save
- 	 * descriptors in the result relation info, so that we can add new index
- 	 * entries for the tuples we add/update.  We need not do this for a
- 	 * DELETE, however, since deletion doesn't affect indexes.
- 	 */
- 	if (resultRelationDesc->rd_rel->relhasindex &&
- 		operation != CMD_DELETE)
- 		ExecOpenIndices(resultRelInfo);
  }
  
  /*
--- 1001,1006 ----
***************
*** 991,1005 **** ExecGetTriggerResultRel(EState *estate, Oid relid)
  
  	/*
  	 * Make the new entry in the right context.  Currently, we don't need any
! 	 * index information in ResultRelInfos used only for triggers, so tell
! 	 * InitResultRelInfo it's a DELETE.
  	 */
  	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
  	rInfo = makeNode(ResultRelInfo);
  	InitResultRelInfo(rInfo,
  					  rel,
  					  0,		/* dummy rangetable index */
- 					  CMD_DELETE,
  					  estate->es_instrument);
  	estate->es_trig_target_relations =
  		lappend(estate->es_trig_target_relations, rInfo);
--- 1056,1068 ----
  
  	/*
  	 * Make the new entry in the right context.  Currently, we don't need any
! 	 * index information in ResultRelInfos used only for triggers.
  	 */
  	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
  	rInfo = makeNode(ResultRelInfo);
  	InitResultRelInfo(rInfo,
  					  rel,
  					  0,		/* dummy rangetable index */
  					  estate->es_instrument);
  	estate->es_trig_target_relations =
  		lappend(estate->es_trig_target_relations, rInfo);
***************
*** 1165,1170 **** ExecutePlan(EState *estate,
--- 1228,1234 ----
  {
  	TupleTableSlot *slot;
  	long		current_tuple_count;
+ 	ListCell	   *lc;
  
  	/*
  	 * initialize local variables
***************
*** 1176,1181 **** ExecutePlan(EState *estate,
--- 1240,1281 ----
  	 */
  	estate->es_direction = direction;
  
+ 	/* If there are any DML WITH statements, process those first */
+ 	foreach(lc, estate->es_prescanstates)
+ 	{
+ 		TupleTableSlot *slot;
+ 		PlanState *ps = (PlanState *) lfirst(lc);
+ 
+ 		for (;;)
+ 		{
+ 			slot = ExecProcNode(ps);
+ 			if (TupIsNull(slot))
+ 				break;
+ 		}
+ 
+ 		/* Need to rewind the CTE */
+ 		if (!IsA(ps, ModifyTableState))
+ 			ExecReScan(ps, NULL);
+ 
+ 		/*
+ 		 * To allow a statement see a previously ran statement's tuples we need to
+ 		 * increment CID and update the snapshot.
+ 		 */
+ 
+ 		CommandCounterIncrement();
+ 
+ 		/*
+ 		 * If this was the last DML WITH statement, don't use a CID unless the
+ 		 * top-level statement requires it.
+ 		 */
+ 		if (!lnext(lc) && ExecTopLevelStmtIsReadOnly(estate->es_plannedstmt))
+ 			estate->es_output_cid = GetCurrentCommandId(false);
+ 		else
+ 			estate->es_output_cid = GetCurrentCommandId(true);
+ 
+ 		estate->es_snapshot->curcid = estate->es_output_cid;
+ 	}
+ 
  	/*
  	 * Loop until we've processed the proper number of tuples from the plan.
  	 */
***************
*** 1957,1963 **** EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
  	 * ExecInitSubPlan expects to be able to find these entries.
  	 * Some of the SubPlans might not be used in the part of the plan tree
  	 * we intend to run, but since it's not easy to tell which, we just
! 	 * initialize them all.
  	 */
  	Assert(estate->es_subplanstates == NIL);
  	foreach(l, parentestate->es_plannedstmt->subplans)
--- 2057,2064 ----
  	 * ExecInitSubPlan expects to be able to find these entries.
  	 * Some of the SubPlans might not be used in the part of the plan tree
  	 * we intend to run, but since it's not easy to tell which, we just
! 	 * initialize them all.  However, we will never run ModifyTable nodes in
! 	 * EvalPlanQual() so don't initialize them.
  	 */
  	Assert(estate->es_subplanstates == NIL);
  	foreach(l, parentestate->es_plannedstmt->subplans)
***************
*** 1965,1971 **** EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
  		Plan	   *subplan = (Plan *) lfirst(l);
  		PlanState  *subplanstate;
  
! 		subplanstate = ExecInitNode(subplan, estate, 0);
  
  		estate->es_subplanstates = lappend(estate->es_subplanstates,
  										   subplanstate);
--- 2066,2076 ----
  		Plan	   *subplan = (Plan *) lfirst(l);
  		PlanState  *subplanstate;
  
! 		/* Don't initialize ModifyTable subplans. */
! 		if (IsA(subplan, ModifyTable))
! 			subplanstate = NULL;
! 		else
! 			subplanstate = ExecInitNode(subplan, estate, 0);
  
  		estate->es_subplanstates = lappend(estate->es_subplanstates,
  										   subplanstate);
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 158,164 **** ExecProcessReturning(ProjectionInfo *projectReturning,
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecInsert(TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EState *estate)
  {
--- 158,165 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecInsert(bool canSetTag,
! 		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EState *estate)
  {
***************
*** 240,246 **** ExecInsert(TupleTableSlot *slot,
  	newId = heap_insert(resultRelationDesc, tuple,
  						estate->es_output_cid, 0, NULL);
  
! 	(estate->es_processed)++;
  	estate->es_lastoid = newId;
  	setLastTid(&(tuple->t_self));
  
--- 241,249 ----
  	newId = heap_insert(resultRelationDesc, tuple,
  						estate->es_output_cid, 0, NULL);
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
! 
  	estate->es_lastoid = newId;
  	setLastTid(&(tuple->t_self));
  
***************
*** 274,280 **** ExecInsert(TupleTableSlot *slot,
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(ItemPointer tupleid,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
--- 277,284 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(bool canSetTag,
! 		   ItemPointer tupleid,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
***************
*** 356,362 **** ldelete:;
  			return NULL;
  	}
  
! 	(estate->es_processed)++;
  
  	/*
  	 * Note: Normally one would think that we have to delete index tuples
--- 360,367 ----
  			return NULL;
  	}
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
  
  	/*
  	 * Note: Normally one would think that we have to delete index tuples
***************
*** 417,423 **** ldelete:;
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(ItemPointer tupleid,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
--- 422,429 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(bool canSetTag,
! 		   ItemPointer tupleid,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
***************
*** 546,552 **** lreplace:;
  			return NULL;
  	}
  
! 	(estate->es_processed)++;
  
  	/*
  	 * Note: instead of having to update the old index tuples associated with
--- 552,559 ----
  			return NULL;
  	}
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
  
  	/*
  	 * Note: instead of having to update the old index tuples associated with
***************
*** 593,607 **** 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");
--- 600,614 ----
  	{
  		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");
***************
*** 619,633 **** 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");
--- 626,640 ----
  	{
  		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");
***************
*** 649,654 **** ExecModifyTable(ModifyTableState *node)
--- 656,662 ----
  	EState *estate = node->ps.state;
  	CmdType operation = node->operation;
  	PlanState *subplanstate;
+ 	ResultRelInfo *resultRelInfo;
  	JunkFilter *junkfilter;
  	TupleTableSlot *slot;
  	TupleTableSlot *planSlot;
***************
*** 664,680 **** 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
--- 672,681 ----
  		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
***************
*** 690,698 **** 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;
  			}
--- 691,699 ----
  			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;
  			}
***************
*** 730,747 **** ExecModifyTable(ModifyTableState *node)
  			if (operation != CMD_DELETE)
  				slot = ExecFilterJunk(junkfilter, slot);
  		}
  
  		switch (operation)
  		{
  			case CMD_INSERT:
! 				slot = ExecInsert(slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(tupleid, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(tupleid, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
--- 731,755 ----
  			if (operation != CMD_DELETE)
  				slot = ExecFilterJunk(junkfilter, slot);
  		}
+ 	
+ 		/*
+ 	 	 * es_result_relation_info must point to the currently active result
+ 	 	 * relation.  We want it to be NULL whenever we're not within
+ 	 	 * ModifyTable, though.
+ 		 */
+ 		estate->es_result_relation_info = resultRelInfo;
  
  		switch (operation)
  		{
  			case CMD_INSERT:
! 				slot = ExecInsert(node->canSetTag, slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(node->canSetTag, tupleid, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(node->canSetTag, tupleid, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
***************
*** 809,833 **** 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;
--- 817,856 ----
  	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
  	mtstate->mt_nplans = nplans;
  	mtstate->operation = operation;
+ 	mtstate->canSetTag = node->canSetTag;
+ 	mtstate->resultRelIndex = node->resultRelIndex;
+ 	mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
+ 
  	/* set up epqstate with dummy subplan pointer for the moment */
  	EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, node->epqParam);
  	mtstate->fireBSTriggers = true;
  
  	/*
  	 * call ExecInitNode on each of the plans to be executed and save the
  	 * results into the array "mt_plans".  Note we *must* set
  	 * estate->es_result_relation_info correctly while we initialize each
  	 * sub-plan; ExecContextForcesOids depends on that!
  	 */
  	i = 0;
+ 	resultRelInfo = mtstate->resultRelInfo;
  	foreach(l, node->plans)
  	{
  		subplan = (Plan *) lfirst(l);
+ 
+ 		/*
+ 		 * If there are indices on the result relation, open them and save
+ 		 * descriptors in the result relation info, so that we can add new index
+ 		 * entries for the tuples we add/update.  We need not do this for a
+ 		 * DELETE, however, since deletion doesn't affect indexes.
+ 		 */
+ 		if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+ 			operation != CMD_DELETE)
+ 			ExecOpenIndices(resultRelInfo);
+ 
+ 		estate->es_result_relation_info = resultRelInfo;
  		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
! 
! 		resultRelInfo++;
  		i++;
  	}
  	estate->es_result_relation_info = NULL;
***************
*** 864,871 **** 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);
--- 887,893 ----
  		/*
  		 * Build a projection for each result rel.
  		 */
! 		resultRelInfo = mtstate->resultRelInfo;
  		foreach(l, node->returningLists)
  		{
  			List	   *rlist = (List *) lfirst(l);
***************
*** 964,970 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  
  		if (junk_filter_needed)
  		{
! 			resultRelInfo = estate->es_result_relations;
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
--- 986,992 ----
  
  		if (junk_filter_needed)
  		{
! 			resultRelInfo = mtstate->resultRelInfo;
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
***************
*** 993,999 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  		else
  		{
  			if (operation == CMD_INSERT)
! 				ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc,
  									subplan->targetlist);
  		}
  	}
--- 1015,1021 ----
  		else
  		{
  			if (operation == CMD_INSERT)
! 				ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
  									subplan->targetlist);
  		}
  	}
*** a/src/backend/executor/nodeSubplan.c
--- b/src/backend/executor/nodeSubplan.c
***************
*** 668,673 **** ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
--- 668,675 ----
  	sstate->planstate = (PlanState *) list_nth(estate->es_subplanstates,
  											   subplan->plan_id - 1);
  
+ 	Assert(sstate->planstate != NULL);
+ 
  	/* Initialize subexpressions */
  	sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
  	sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent);
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 84,89 **** _copyPlannedStmt(PlannedStmt *from)
--- 84,90 ----
  	COPY_NODE_FIELD(resultRelations);
  	COPY_NODE_FIELD(utilityStmt);
  	COPY_NODE_FIELD(intoClause);
+ 	COPY_SCALAR_FIELD(hasDmlWith);
  	COPY_NODE_FIELD(subplans);
  	COPY_BITMAPSET_FIELD(rewindPlanIDs);
  	COPY_NODE_FIELD(rowMarks);
***************
*** 171,177 **** _copyModifyTable(ModifyTable *from)
--- 172,180 ----
  	 * copy remainder of node
  	 */
  	COPY_SCALAR_FIELD(operation);
+ 	COPY_SCALAR_FIELD(canSetTag);
  	COPY_NODE_FIELD(resultRelations);
+ 	COPY_SCALAR_FIELD(resultRelIndex);
  	COPY_NODE_FIELD(plans);
  	COPY_NODE_FIELD(returningLists);
  	COPY_NODE_FIELD(rowMarks);
***************
*** 2260,2265 **** _copyInsertStmt(InsertStmt *from)
--- 2263,2269 ----
  	COPY_NODE_FIELD(cols);
  	COPY_NODE_FIELD(selectStmt);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
***************
*** 2273,2278 **** _copyDeleteStmt(DeleteStmt *from)
--- 2277,2283 ----
  	COPY_NODE_FIELD(usingClause);
  	COPY_NODE_FIELD(whereClause);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
***************
*** 2287,2292 **** _copyUpdateStmt(UpdateStmt *from)
--- 2292,2298 ----
  	COPY_NODE_FIELD(whereClause);
  	COPY_NODE_FIELD(fromClause);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 888,893 **** _equalInsertStmt(InsertStmt *a, InsertStmt *b)
--- 888,894 ----
  	COMPARE_NODE_FIELD(cols);
  	COMPARE_NODE_FIELD(selectStmt);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
***************
*** 899,904 **** _equalDeleteStmt(DeleteStmt *a, DeleteStmt *b)
--- 900,906 ----
  	COMPARE_NODE_FIELD(usingClause);
  	COMPARE_NODE_FIELD(whereClause);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
***************
*** 911,916 **** _equalUpdateStmt(UpdateStmt *a, UpdateStmt *b)
--- 913,919 ----
  	COMPARE_NODE_FIELD(whereClause);
  	COMPARE_NODE_FIELD(fromClause);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 2394,2399 **** bool
--- 2394,2449 ----
  					return true;
  			}
  			break;
+ 		case T_InsertStmt:
+ 			{
+ 				InsertStmt *stmt = (InsertStmt *) node;
+ 
+ 				if (walker(stmt->relation, context))
+ 					return true;
+ 				if (walker(stmt->cols, context))
+ 					return true;
+ 				if (walker(stmt->selectStmt, context))
+ 					return true;
+ 				if (walker(stmt->returningList, context))
+ 					return true;
+ 				if (walker(stmt->withClause, context))
+ 					return true;
+ 			}
+ 			break;
+ 		case T_UpdateStmt:
+ 			{
+ 				UpdateStmt *stmt = (UpdateStmt *) node;
+ 
+ 				if (walker(stmt->relation, context))
+ 					return true;
+ 				if (walker(stmt->targetList, context))
+ 					return true;
+ 				if (walker(stmt->whereClause, context))
+ 					return true;
+ 				if (walker(stmt->fromClause, context))
+ 					return true;
+ 				if (walker(stmt->returningList, context))
+ 					return true;
+ 				if (walker(stmt->withClause, context))
+ 					return true;
+ 			}
+ 			break;
+ 		case T_DeleteStmt:
+ 			{
+ 				DeleteStmt *stmt = (DeleteStmt *) node;
+ 
+ 				if (walker(stmt->relation, context))
+ 					return true;
+ 				if (walker(stmt->usingClause, context))
+ 					return true;
+ 				if (walker(stmt->whereClause, context))
+ 					return true;
+ 				if (walker(stmt->returningList, context))
+ 					return true;
+ 				if (walker(stmt->withClause, context))
+ 					return true;
+ 			}
+ 			break;
  		case T_A_Expr:
  			{
  				A_Expr	   *expr = (A_Expr *) node;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 327,332 **** _outModifyTable(StringInfo str, ModifyTable *node)
--- 327,333 ----
  
  	WRITE_ENUM_FIELD(operation, CmdType);
  	WRITE_NODE_FIELD(resultRelations);
+ 	WRITE_INT_FIELD(resultRelIndex);
  	WRITE_NODE_FIELD(plans);
  	WRITE_NODE_FIELD(returningLists);
  	WRITE_NODE_FIELD(rowMarks);
***************
*** 1533,1538 **** _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
--- 1534,1540 ----
  	WRITE_NODE_FIELD(finalrowmarks);
  	WRITE_NODE_FIELD(relationOids);
  	WRITE_NODE_FIELD(invalItems);
+ 	WRITE_NODE_FIELD(resultRelations);
  	WRITE_UINT_FIELD(lastPHId);
  	WRITE_BOOL_FIELD(transientPlan);
  }
***************
*** 1548,1554 **** _outPlannerInfo(StringInfo str, PlannerInfo *node)
  	WRITE_UINT_FIELD(query_level);
  	WRITE_NODE_FIELD(join_rel_list);
  	WRITE_INT_FIELD(join_cur_level);
- 	WRITE_NODE_FIELD(resultRelations);
  	WRITE_NODE_FIELD(init_plans);
  	WRITE_NODE_FIELD(cte_plan_ids);
  	WRITE_NODE_FIELD(eq_classes);
--- 1550,1555 ----
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 3754,3760 **** make_result(PlannerInfo *root,
   * to make it look better sometime.
   */
  ModifyTable *
! make_modifytable(CmdType operation, List *resultRelations,
  				 List *subplans, List *returningLists,
  				 List *rowMarks, int epqParam)
  {
--- 3754,3761 ----
   * to make it look better sometime.
   */
  ModifyTable *
! make_modifytable(CmdType operation, bool canSetTag,
! 				 List *resultRelations,
  				 List *subplans, List *returningLists,
  				 List *rowMarks, int epqParam)
  {
***************
*** 3804,3809 **** make_modifytable(CmdType operation, List *resultRelations,
--- 3805,3811 ----
  		node->plan.targetlist = NIL;
  
  	node->operation = operation;
+ 	node->canSetTag = canSetTag;
  	node->resultRelations = resultRelations;
  	node->plans = subplans;
  	node->returningLists = returningLists;
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 160,165 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
--- 160,167 ----
  	glob->finalrowmarks = NIL;
  	glob->relationOids = NIL;
  	glob->invalItems = NIL;
+ 	glob->hasDmlWith = 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->hasDmlWith = glob->hasDmlWith;
  	result->subplans = glob->subplans;
  	result->rewindPlanIDs = glob->rewindPlanIDs;
  	result->rowMarks = glob->finalrowmarks;
***************
*** 541,547 **** subquery_planner(PlannerGlobal *glob, Query *parse,
  				rowMarks = root->rowMarks;
  
  			plan = (Plan *) make_modifytable(parse->commandType,
! 											 copyObject(root->resultRelations),
  											 list_make1(plan),
  											 returningLists,
  											 rowMarks,
--- 544,551 ----
  				rowMarks = root->rowMarks;
  
  			plan = (Plan *) make_modifytable(parse->commandType,
! 											 parse->canSetTag,
! 											 list_make1_int(parse->resultRelation),
  											 list_make1(plan),
  											 returningLists,
  											 rowMarks,
***************
*** 706,718 **** inheritance_planner(PlannerInfo *root)
  	Query	   *parse = root->parse;
  	int			parentRTindex = parse->resultRelation;
  	List	   *subplans = NIL;
- 	List	   *resultRelations = NIL;
  	List	   *returningLists = NIL;
  	List	   *rtable = NIL;
  	List	   *rowMarks;
  	List	   *tlist;
  	PlannerInfo subroot;
  	ListCell   *l;
  
  	foreach(l, root->append_rel_list)
  	{
--- 710,722 ----
  	Query	   *parse = root->parse;
  	int			parentRTindex = parse->resultRelation;
  	List	   *subplans = NIL;
  	List	   *returningLists = NIL;
  	List	   *rtable = NIL;
  	List	   *rowMarks;
  	List	   *tlist;
  	PlannerInfo subroot;
  	ListCell   *l;
+ 	List	   *resultRelations = NIL;
  
  	foreach(l, root->append_rel_list)
  	{
***************
*** 772,779 **** inheritance_planner(PlannerInfo *root)
  		}
  	}
  
- 	root->resultRelations = resultRelations;
- 
  	/* Mark result as unordered (probably unnecessary) */
  	root->query_pathkeys = NIL;
  
--- 776,781 ----
***************
*** 783,789 **** inheritance_planner(PlannerInfo *root)
  	 */
  	if (subplans == NIL)
  	{
- 		root->resultRelations = list_make1_int(parentRTindex);
  		/* although dummy, it must have a valid tlist for executor */
  		tlist = preprocess_targetlist(root, parse->targetList);
  		return (Plan *) make_result(root,
--- 785,790 ----
***************
*** 818,824 **** inheritance_planner(PlannerInfo *root)
  
  	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
  	return (Plan *) make_modifytable(parse->commandType,
! 									 copyObject(root->resultRelations),
  									 subplans, 
  									 returningLists,
  									 rowMarks,
--- 819,826 ----
  
  	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
  	return (Plan *) make_modifytable(parse->commandType,
! 									 parse->canSetTag,
! 									 resultRelations,
  									 subplans, 
  									 returningLists,
  									 rowMarks,
***************
*** 1668,1679 **** grouping_planner(PlannerInfo *root, double tuple_fraction)
  										  count_est);
  	}
  
- 	/* Compute result-relations list if needed */
- 	if (parse->resultRelation)
- 		root->resultRelations = list_make1_int(parse->resultRelation);
- 	else
- 		root->resultRelations = NIL;
- 
  	/*
  	 * Return the actual output ordering in query_pathkeys for possible use by
  	 * an outer query level.
--- 1670,1675 ----
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 520,525 **** set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
--- 520,529 ----
  											  (Plan *) lfirst(l),
  											  rtoffset);
  				}
+ 
+ 				splan->resultRelIndex = list_length(glob->resultRelations);
+ 				glob->resultRelations = list_concat(glob->resultRelations,
+ 													splan->resultRelations);
  			}
  			break;
  		case T_Append:
*** a/src/backend/optimizer/plan/subselect.c
--- b/src/backend/optimizer/plan/subselect.c
***************
*** 873,888 **** SS_process_ctes(PlannerInfo *root)
  		Bitmapset  *tmpset;
  		int			paramid;
  		Param	   *prm;
  
  		/*
! 		 * Ignore CTEs that are not actually referenced anywhere.
  		 */
! 		if (cte->cterefcount == 0)
  		{
  			/* Make a dummy entry in cte_plan_ids */
  			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
  			continue;
  		}
  
  		/*
  		 * Copy the source Query node.	Probably not necessary, but let's keep
--- 873,906 ----
  		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")));
+ 
+ 			if (root->query_level > 1)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("DML WITH is only allowed at the top level")));
+ 
+ 			root->glob->hasDmlWith = true;
+ 		}
  
  		/*
  		 * Copy the source Query node.	Probably not necessary, but let's keep
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 290,295 **** transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
--- 290,302 ----
  
  	qry->distinctClause = NIL;
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * The USING clause is non-standard SQL syntax, and is equivalent in
  	 * functionality to the FROM list that can be specified for UPDATE. The
***************
*** 342,347 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 349,361 ----
  	qry->commandType = CMD_INSERT;
  	pstate->p_is_insert = true;
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
  	 * VALUES list, or general SELECT input.  We special-case VALUES, both for
***************
*** 366,373 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
  		pstate->p_relnamespace = NIL;
  		sub_varnamespace = pstate->p_varnamespace;
  		pstate->p_varnamespace = NIL;
- 		/* There can't be any outer WITH to worry about */
- 		Assert(pstate->p_ctenamespace == NIL);
  	}
  	else
  	{
--- 380,385 ----
***************
*** 1735,1740 **** transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
--- 1747,1759 ----
  										 true,
  										 ACL_UPDATE);
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * the FROM clause is non-standard SQL syntax. We used to be able to do
  	 * this with REPLACE in POSTQUEL so we keep the feature.
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 429,435 **** static TypeName *TableFuncTypeName(List *columns);
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
! %type <with> 	with_clause
  %type <list>	cte_list
  
  %type <list>	window_clause window_definition_list opt_partition_clause
--- 429,435 ----
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
! %type <with> 	with_clause opt_with_clause
  %type <list>	cte_list
  
  %type <list>	window_clause window_definition_list opt_partition_clause
***************
*** 7072,7082 **** DeallocateStmt: DEALLOCATE name
   *****************************************************************************/
  
  InsertStmt:
! 			INSERT INTO qualified_name insert_rest returning_clause
  				{
! 					$4->relation = $3;
! 					$4->returningList = $5;
! 					$$ = (Node *) $4;
  				}
  		;
  
--- 7072,7083 ----
   *****************************************************************************/
  
  InsertStmt:
! 			opt_with_clause INSERT INTO qualified_name insert_rest returning_clause
  				{
! 					$5->relation = $4;
! 					$5->returningList = $6;
! 					$5->withClause = $1;
! 					$$ = (Node *) $5;
  				}
  		;
  
***************
*** 7132,7145 **** returning_clause:
   *
   *****************************************************************************/
  
! DeleteStmt: DELETE_P FROM relation_expr_opt_alias
  			using_clause where_or_current_clause returning_clause
  				{
  					DeleteStmt *n = makeNode(DeleteStmt);
! 					n->relation = $3;
! 					n->usingClause = $4;
! 					n->whereClause = $5;
! 					n->returningList = $6;
  					$$ = (Node *)n;
  				}
  		;
--- 7133,7147 ----
   *
   *****************************************************************************/
  
! DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias
  			using_clause where_or_current_clause returning_clause
  				{
  					DeleteStmt *n = makeNode(DeleteStmt);
! 					n->relation = $4;
! 					n->usingClause = $5;
! 					n->whereClause = $6;
! 					n->returningList = $7;
! 					n->withClause = $1;
  					$$ = (Node *)n;
  				}
  		;
***************
*** 7194,7211 **** opt_nowait:	NOWAIT							{ $$ = TRUE; }
   *
   *****************************************************************************/
  
! UpdateStmt: UPDATE relation_expr_opt_alias
  			SET set_clause_list
  			from_clause
  			where_or_current_clause
  			returning_clause
  				{
  					UpdateStmt *n = makeNode(UpdateStmt);
! 					n->relation = $2;
! 					n->targetList = $4;
! 					n->fromClause = $5;
! 					n->whereClause = $6;
! 					n->returningList = $7;
  					$$ = (Node *)n;
  				}
  		;
--- 7196,7214 ----
   *
   *****************************************************************************/
  
! UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
  			SET set_clause_list
  			from_clause
  			where_or_current_clause
  			returning_clause
  				{
  					UpdateStmt *n = makeNode(UpdateStmt);
! 					n->relation = $3;
! 					n->targetList = $5;
! 					n->fromClause = $6;
! 					n->whereClause = $7;
! 					n->returningList = $8;
! 					n->withClause = $1;
  					$$ = (Node *)n;
  				}
  		;
***************
*** 7531,7536 **** with_clause:
--- 7534,7543 ----
  			}
  		;
  
+ opt_with_clause:
+ 		with_clause								{ $$ = $1; }
+ 		| /*EMPTY*/								{ $$ = NULL; }
+ 
  cte_list:
  		common_table_expr						{ $$ = list_make1($1); }
  		| cte_list ',' common_table_expr		{ $$ = lappend($1, $3); }
***************
*** 7545,7550 **** common_table_expr:  name opt_name_list AS select_with_parens
--- 7552,7584 ----
  				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,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;
***************
*** 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,
--- 595,613 ----
  		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
***************
*** 314,323 **** markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 				ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
  									   attnum);
  				if (ste == NULL || ste->resjunk)
  					elog(ERROR, "subquery %s does not have attribute %d",
--- 314,333 ----
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
+ 				List		*cteList;
+ 				Query		*ctequery;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 
! 				ctequery = (Query *) cte->ctequery;
! 
! 				if (ctequery->commandType == CMD_SELECT)
! 					cteList = ctequery->targetList;
! 				else
! 					cteList = ctequery->returningList;
! 
! 				ste = get_tle_by_resno(cteList,
  									   attnum);
  				if (ste == NULL || ste->resjunk)
  					elog(ERROR, "subquery %s does not have attribute %d",
***************
*** 1345,1355 **** expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 				ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
! 									   attnum);
  				if (ste == NULL || ste->resjunk)
  					elog(ERROR, "subquery %s does not have attribute %d",
  						 rte->eref->aliasname, attnum);
--- 1355,1374 ----
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
+ 				List		*cteList;
+ 				Query		*ctequery;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 
! 				ctequery = (Query *) cte->ctequery;
! 
! 				if (ctequery->commandType == CMD_SELECT)
! 					cteList = ctequery->targetList;
! 				else
! 					cteList = ctequery->returningList;
! 
! 				ste = get_tle_by_resno(cteList, attnum);
  				if (ste == NULL || ste->resjunk)
  					elog(ERROR, "subquery %s does not have attribute %d",
  						 rte->eref->aliasname, attnum);
***************
*** 1372,1378 **** expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
  						 levelsup++)
  						pstate = pstate->parentParseState;
  					mypstate.parentParseState = pstate;
! 					mypstate.p_rtable = ((Query *) cte->ctequery)->rtable;
  					/* don't bother filling the rest of the fake pstate */
  
  					return expandRecordVariable(&mypstate, (Var *) expr, 0);
--- 1391,1397 ----
  						 levelsup++)
  						pstate = pstate->parentParseState;
  					mypstate.parentParseState = pstate;
! 					mypstate.p_rtable = ctequery->rtable;
  					/* don't bother filling the rest of the fake pstate */
  
  					return expandRecordVariable(&mypstate, (Var *) expr, 0);
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 1632,1637 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1632,1641 ----
  	bool		returning = false;
  	Query	   *qual_product = NULL;
  	List	   *rewritten = NIL;
+ 	ListCell	*lc1;
+ 	CommonTableExpr	*cte;
+ 	Query		*ctequery;
+ 	List		*newstuff;
  
  	/*
  	 * If the statement is an update, insert or delete - fire rules on it.
***************
*** 1749,1755 **** RewriteQuery(Query *parsetree, List *rewrite_events)
  				foreach(n, product_queries)
  				{
  					Query	   *pt = (Query *) lfirst(n);
- 					List	   *newstuff;
  
  					newstuff = RewriteQuery(pt, rewrite_events);
  					rewritten = list_concat(rewritten, newstuff);
--- 1753,1758 ----
***************
*** 1804,1809 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1807,1874 ----
  	}
  
  	/*
+ 	 * 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);
+ 
+ 			cte->ctequery = (Node *) linitial(newstuff);
+ 
+ 			/*
+ 			 * Only the top-level statement sets the command tag.
+ 			 */
+ 			/* this query won't set the command tag */
+ 			((Query *) cte->ctequery)->canSetTag = false;
+ 		}
+ 	}
+ 
+ 	/*
  	 * For INSERTs, the original query is done first; for UPDATE/DELETE, it is
  	 * done last.  This is needed because update and delete rule actions might
  	 * not do anything if they are invoked after the update or delete is
*** a/src/backend/tcop/pquery.c
--- b/src/backend/tcop/pquery.c
***************
*** 293,298 **** ChoosePortalStrategy(List *stmts)
--- 293,299 ----
  			if (pstmt->canSetTag)
  			{
  				if (pstmt->commandType == CMD_SELECT &&
+ 					!pstmt->hasDmlWith &&
  					pstmt->utilityStmt == NULL &&
  					pstmt->intoClause == NULL)
  					return PORTAL_ONE_SELECT;
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 3980,3988 **** 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",
--- 3980,3995 ----
  				}
  				if (lc != NULL)
  				{
! 					Query		*ctequery = (Query *) cte->ctequery;
! 					List		*ctelist;
! 					TargetEntry	*ste;
! 
! 					if (ctequery->commandType != CMD_SELECT)
! 						ctelist = ctequery->returningList;
! 					else
! 						ctelist = ctequery->targetList;
! 
! 					ste = get_tle_by_resno(ctelist, attnum);
  
  					if (ste == NULL || ste->resjunk)
  						elog(ERROR, "subquery %s does not have attribute %d",
*** a/src/backend/utils/time/snapmgr.c
--- b/src/backend/utils/time/snapmgr.c
***************
*** 375,380 **** RegisterSnapshot(Snapshot snapshot)
--- 375,401 ----
  }
  
  /*
+  * RegisterSnapshot
+  *		Copies a snapshot and registers the copy as being in use by the current
+  *		resource owner
+  *
+  * If InvalidSnapshot is passed, it is not registered.
+  */
+ Snapshot
+ RegisterSnapshotCopy(Snapshot snapshot)
+ {
+ 	Snapshot	snap;
+ 
+ 	if (snapshot == InvalidSnapshot)
+ 		return InvalidSnapshot;
+ 
+ 	snap = CopySnapshot(snapshot);
+ 
+ 	return RegisterSnapshotOnOwner(snap, CurrentResourceOwner);
+ }
+ 
+ 
+ /*
   * RegisterSnapshotOnOwner
   *		As above, but use the specified resource owner
   */
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 160,166 **** extern void ExecutorRewind(QueryDesc *queryDesc);
  extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
- 				  CmdType operation,
  				  int instrument_options);
  extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
  extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
--- 160,165 ----
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 376,381 **** typedef struct EState
--- 376,382 ----
  
  	List	   *es_exprcontexts;	/* List of ExprContexts within EState */
  
+ 	List	   *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
***************
*** 850,856 **** 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? */
--- 850,856 ----
  	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? */
***************
*** 880,885 **** typedef struct InsertStmt
--- 880,886 ----
  	List	   *cols;			/* optional: names of the target columns */
  	Node	   *selectStmt;		/* the source SELECT/VALUES, or NULL */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } InsertStmt;
  
  /* ----------------------
***************
*** 893,898 **** typedef struct DeleteStmt
--- 894,900 ----
  	List	   *usingClause;	/* optional using clause for more tables */
  	Node	   *whereClause;	/* qualifications */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } DeleteStmt;
  
  /* ----------------------
***************
*** 907,912 **** typedef struct UpdateStmt
--- 909,915 ----
  	Node	   *whereClause;	/* qualifications */
  	List	   *fromClause;		/* optional from clause for more tables */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } UpdateStmt;
  
  /* ----------------------
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 55,60 **** typedef struct PlannedStmt
--- 55,62 ----
  
  	IntoClause *intoClause;		/* target for SELECT INTO / CREATE TABLE AS */
  
+ 	bool		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
***************
*** 77,83 **** extern SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
  		   long numGroups, double outputRows);
  extern Result *make_result(PlannerInfo *root, List *tlist,
  			Node *resconstantqual, Plan *subplan);
! extern ModifyTable *make_modifytable(CmdType operation, List *resultRelations,
  									 List *subplans, List *returningLists,
  									 List *rowMarks, int epqParam);
  extern bool is_projection_capable_plan(Plan *plan);
--- 77,84 ----
  		   long numGroups, double outputRows);
  extern Result *make_result(PlannerInfo *root, List *tlist,
  			Node *resconstantqual, Plan *subplan);
! extern ModifyTable *make_modifytable(CmdType operation, bool canSetTag,
! 									 List *resultRelations,
  									 List *subplans, List *returningLists,
  									 List *rowMarks, int epqParam);
  extern bool is_projection_capable_plan(Plan *plan);
*** a/src/include/utils/snapmgr.h
--- b/src/include/utils/snapmgr.h
***************
*** 34,39 **** extern Snapshot GetActiveSnapshot(void);
--- 34,40 ----
  extern bool ActiveSnapshotSet(void);
  
  extern Snapshot RegisterSnapshot(Snapshot snapshot);
+ extern Snapshot RegisterSnapshotCopy(Snapshot snapshot);
  extern void UnregisterSnapshot(Snapshot snapshot);
  extern Snapshot RegisterSnapshotOnOwner(Snapshot snapshot, ResourceOwner owner);
  extern void UnregisterSnapshotFromOwner(Snapshot snapshot, ResourceOwner owner);
*** a/src/test/regress/expected/with.out
--- b/src/test/regress/expected/with.out
***************
*** 1026,1028 **** SELECT * FROM t;
--- 1026,1249 ----
   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)
+ 
+ -- the top level statement can update rows updated by DML WITH
+ WITH t AS (
+ 	UPDATE y SET a = a-11
+ ), t2 AS (
+ 	DELETE FROM y WHERE a <= 5
+ )
+ UPDATE y SET a=a+1 RETURNING *;
+  a  
+ ----
+   7
+   8
+   9
+  10
+  11
+ (5 rows)
+ 
+ -- t2 can update tuples updated by t
+ WITH t AS (
+ 	UPDATE y SET a=a-100
+ ), t2 AS (
+ 	UPDATE y SET a=a+94
+ )
+ SELECT * FROM y;
+  a 
+ ---
+  1
+  2
+  3
+  4
+  5
+ (5 rows)
+ 
+ -- forward reference
+ WITH RECURSIVE t AS (
+ 	INSERT INTO y
+ 		SELECT * FROM t2 WHERE a < 10
+ ), t2 AS (
+ 	UPDATE y SET a=a+6 RETURNING *
+ )
+ SELECT * FROM y;
+  a  
+ ----
+   7
+   8
+   9
+  10
+  11
+   7
+   8
+   9
+ (8 rows)
+ 
+ -- unconditional DO INSTEAD rule
+ CREATE RULE y_rule AS ON DELETE TO y DO INSTEAD INSERT INTO y VALUES(0);
+ WITH t AS (
+ 	DELETE FROM y
+ )
+ SELECT * FROM y;
+  a  
+ ----
+   7
+   8
+   9
+  10
+  11
+   7
+   8
+   9
+   0
+ (9 rows)
+ 
+ DROP RULE y_rule ON y;
+ -- 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
+ -- 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
***************
*** 500,502 **** WITH RECURSIVE t(j) AS (
--- 500,636 ----
      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;
+ 
+ -- the top level statement can update rows updated by DML WITH
+ WITH t AS (
+ 	UPDATE y SET a = a-11
+ ), t2 AS (
+ 	DELETE FROM y WHERE a <= 5
+ )
+ UPDATE y SET a=a+1 RETURNING *;
+ 
+ -- t2 can update tuples updated by t
+ WITH t AS (
+ 	UPDATE y SET a=a-100
+ ), t2 AS (
+ 	UPDATE y SET a=a+94
+ )
+ SELECT * FROM y;
+ 
+ -- forward reference
+ WITH RECURSIVE t AS (
+ 	INSERT INTO y
+ 		SELECT * FROM t2 WHERE a < 10
+ ), t2 AS (
+ 	UPDATE y SET a=a+6 RETURNING *
+ )
+ SELECT * FROM y;
+ 
+ -- unconditional DO INSTEAD rule
+ CREATE RULE y_rule AS ON DELETE TO y DO INSTEAD INSERT INTO y VALUES(0);
+ WITH t AS (
+ 	DELETE FROM y
+ )
+ SELECT * FROM y;
+ DROP RULE y_rule ON y;
+ 
+ -- 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;
#3Takahiro Itagaki
itagaki.takahiro@oss.ntt.co.jp
In reply to: Marko Tiikkaja (#2)
Re: Writeable CTEs patch

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

Here's an updated patch. Only changes from the previous patch are
fixing the above issue and a regression test for it.

A brief report for of the patch:

* The patch has the following error cases, and also have one regression
test for each case.

- DML WITH is not allowed in a cursor declaration
- DML WITH is not allowed in a view definition
- DML WITH without RETURNING is only allowed inside an unreferenced CTE
- DML WITH is only allowed at the top level
- Recursive DML WITH statements are not supported
^-- might be better if "DML WITH cannot have the self-reference" or so?

- Conditional DO INSTEAD rules are not supported in DML WITH statements
- DO ALSO rules are not supported in DML WITH statements
- Multi-statement DO INSTEAD rules are not supported in DML WITH statements
- DO INSTEAD NOTHING rules are not supported in DML WITH statements

* In the regression tests, almost all of them don't have ORDER BY clause.
They just work, but we might need ORDER BY to get robust output.
What did we do in other regression tests?

* I feel odd the following paragraph in the docs, but should be checked by
native English speakers.

*** a/doc/src/sgml/ref/create_rule.sgml
--- b/doc/src/sgml/ref/create_rule.sgml
***************
*** 222,227 **** CREATE [ OR REPLACE ] RULE <replaceable class="parameter">name</replaceable> AS
--- 222,234 ----
    </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
                       ^-- and? which comma is the sentence separator?
+    implemented.
     ^-- might be "available" rather than "implemented"?
+   </para>

Regards,
---
Takahiro Itagaki
NTT Open Source Software Center

#4Marko Tiikkaja
marko.tiikkaja@cs.helsinki.fi
In reply to: Takahiro Itagaki (#3)
Re: Writeable CTEs patch

On 2010-02-05 07:14 UTC+2, Takahiro Itagaki wrote:

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

Here's an updated patch. Only changes from the previous patch are
fixing the above issue and a regression test for it.

* In the regression tests, almost all of them don't have ORDER BY clause.
They just work, but we might need ORDER BY to get robust output.
What did we do in other regression tests?

Looking at with.sql, it seems to use ORDER BY when it accesses data from
a table. But obviously we can't do this if want to test
INSERT/UPDATE/DELETE .. RETURNING at the top level and returning.sql
seems to be relying on the fact that they come out in the same order
every time.

Regards,
Marko Tiikkaja

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Takahiro Itagaki (#3)
Re: Writeable CTEs patch

Takahiro Itagaki <itagaki.takahiro@oss.ntt.co.jp> writes:

* In the regression tests, almost all of them don't have ORDER BY clause.
They just work, but we might need ORDER BY to get robust output.
What did we do in other regression tests?

We add ORDER BY only when experience shows it's necessary. The
reasoning is explained in regress.sgml:

You might wonder why we don't order all the regression test queries explicitly
to get rid of this issue once and for all. The reason is that that would
make the regression tests less useful, not more, since they'd tend
to exercise query plan types that produce ordered results to the
exclusion of those that don't.

regards, tom lane

#6Robert Haas
robertmhaas@gmail.com
In reply to: Marko Tiikkaja (#2)
Re: Writeable CTEs patch

On Thu, Feb 4, 2010 at 11:57 AM, Marko Tiikkaja
<marko.tiikkaja@cs.helsinki.fi> wrote:

On 2010-02-04 18:04 UTC+2, I wrote:

While working on the docs, I noticed one problem with the patch itself:
it doesn't handle multi-statement DO INSTEAD rules correctly.  I'm going
to submit a fix for that later.

Here's an updated patch.  Only changes from the previous patch are
fixing the above issue and a regression test for it.

The comments on the parts I asked about before are much better in this
version. A few other things that I notice:

- I'm not sure that canSetTag is the right name for the additional
argument to ExecInsert/ExecUpdate/ExecDelete. OTOH, I'm not sure it's
the wrong name either. But should we use something like
isTopLevelQuery?

- It appears that we pull out all of the DML statements first and run
them in order, but I'm not sure that's the right thing to do.
Consider:

WITH x AS (INSERT ...), y AS (SELECT ...), z AS (INSERT ...) SELECT ...

I would assume we would do x, CCI, do y, do z, CCI, do main query, but
I don't think that's what this implements. The user might be
surprised to find out that y sees the effects of z.

- I think that the comment in analyzeCTE that says /* Check that we
got something reasonable */ could be fleshed out a bit. You could
still reference transformRangeSubselect, for example, but then explain
why the checks here are different (viz, CTEs can contain DML).

- The comment for RegisterSnapshotCopy identifies the function name as
RegisterSnapshot; I think this is a copy-and-pasteo.

- It seems like the gram.y changes for common_table_expr might benefit
from some factoring; that is, create a production (or find a suitable
existing one) for "statements of the sort that can appear within
CTEs", and then use that in common_table_expr. Or maybe this doesn't
work; I haven't tried it.

- I still don't much like the idea of using DML WITH in error
messages. One idea I had (which might suck, but I'm just throwing it
out there) is to change hasDmlWith to an integer bitmap with a bit for
each of insert, update, and delete. But it may be better still to
just rephrase the error messages. Could we just write, e.g.
"non-SELECT statements are not allowed within a cursor declaration?"
Or we could say "INSERT, UPDATE, and DELETE statements are not allowed
within a cursor declaration", but I'm thinking we may want to allow
things like COPY and EXPLAIN inside CTEs in the future, too, and
they'll presumably be treated similarly to DML.

For the record, Tom or whoever should feel to swoop in here at any
time, or add to any of this. I'm just making suggestions until the
big guns show up.

...Robert

#7Marko Tiikkaja
marko.tiikkaja@cs.helsinki.fi
In reply to: Robert Haas (#6)
Re: Writeable CTEs patch

On 2010-02-08 18:42 +0200, Robert Haas wrote:

On Thu, Feb 4, 2010 at 11:57 AM, Marko Tiikkaja

Here's an updated patch. Only changes from the previous patch are
fixing the above issue and a regression test for it.

- I'm not sure that canSetTag is the right name for the additional
argument to ExecInsert/ExecUpdate/ExecDelete. OTOH, I'm not sure it's
the wrong name either. But should we use something like
isTopLevelQuery?

No objection to changing that.

- It appears that we pull out all of the DML statements first and run
them in order, but I'm not sure that's the right thing to do.
Consider:

WITH x AS (INSERT ...), y AS (SELECT ...), z AS (INSERT ...) SELECT ...

I would assume we would do x, CCI, do y, do z, CCI, do main query, but
I don't think that's what this implements. The user might be
surprised to find out that y sees the effects of z.

Hmm. Right. That sounds like the right thing to do. Another option
(which I seem to recall we've discussed before) is to not allow any
SELECT statements between DML WITHs, but I think this is what we should
go for.

- I think that the comment in analyzeCTE that says /* Check that we
got something reasonable */ could be fleshed out a bit. You could
still reference transformRangeSubselect, for example, but then explain
why the checks here are different (viz, CTEs can contain DML).

Ok, I'll look into that.

- The comment for RegisterSnapshotCopy identifies the function name as
RegisterSnapshot; I think this is a copy-and-pasteo.

You're right. Will fix.

- It seems like the gram.y changes for common_table_expr might benefit
from some factoring; that is, create a production (or find a suitable
existing one) for "statements of the sort that can appear within
CTEs", and then use that in common_table_expr. Or maybe this doesn't
work; I haven't tried it.

My bison-fu is not exactly strong, but I can look at the feasibility of
that.

- I still don't much like the idea of using DML WITH in error
messages. One idea I had (which might suck, but I'm just throwing it
out there) is to change hasDmlWith to an integer bitmap with a bit for
each of insert, update, and delete. But it may be better still to
just rephrase the error messages.

I don't see how that would work. We'd still potentially have many
different types of DML operations to deal with and that wouldn't help at
all at distinguishing which operation actually caused the error. Or did
I misunderstand?

Could we just write, e.g.
"non-SELECT statements are not allowed within a cursor declaration?"
Or we could say "INSERT, UPDATE, and DELETE statements are not allowed
within a cursor declaration", but I'm thinking we may want to allow
things like COPY and EXPLAIN inside CTEs in the future, too, and
they'll presumably be treated similarly to DML.

"INSERT, UPDATE and DELETE" is quite long and "non-SELECT" is a bit
clumsy IMO. But I don't really have anything better to offer, either.

Regards,
Marko Tiikkaja

#8Robert Haas
robertmhaas@gmail.com
In reply to: Marko Tiikkaja (#7)
Re: Writeable CTEs patch

On Mon, Feb 8, 2010 at 1:01 PM, Marko Tiikkaja
<marko.tiikkaja@cs.helsinki.fi> wrote:

Could we just write, e.g.
"non-SELECT statements are not allowed within a cursor declaration?"
Or we could say "INSERT, UPDATE, and DELETE statements are not allowed
within a cursor declaration", but I'm thinking we may want to allow
things like COPY and EXPLAIN inside CTEs in the future, too, and
they'll presumably be treated similarly to DML.

"INSERT, UPDATE and DELETE" is quite long and "non-SELECT" is a bit
clumsy IMO.  But I don't really have anything better to offer, either.

Yeah, I don't feel good about "INSERT, UPDATE, and DELETE" because in
most of the relevant contexts the list might get longer if in the
future we allow things like EXPLAIN and COPY within CTEs. I think
"Non-SELECT statement" is reasonably clear, though; people might not
know which things are statements, but the message implies that SELECT
is one such thing, and not the one that's the problem, which should
get them pointed in the right direction.

...Robert

#9Alvaro Herrera
alvherre@commandprompt.com
In reply to: Robert Haas (#8)
Re: Writeable CTEs patch

Robert Haas escribi�:

Yeah, I don't feel good about "INSERT, UPDATE, and DELETE" because in
most of the relevant contexts the list might get longer if in the
future we allow things like EXPLAIN and COPY within CTEs. I think
"Non-SELECT statement" is reasonably clear, though; people might not
know which things are statements, but the message implies that SELECT
is one such thing, and not the one that's the problem, which should
get them pointed in the right direction.

"DML statements other than SELECT" perhaps?

--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

#10Alvaro Herrera
alvherre@commandprompt.com
In reply to: Robert Haas (#8)
Re: Writeable CTEs patch

Robert Haas escribi�:

On Mon, Feb 8, 2010 at 1:01 PM, Marko Tiikkaja
<marko.tiikkaja@cs.helsinki.fi> wrote:

Could we just write, e.g.
"non-SELECT statements are not allowed within a cursor declaration?"
Or we could say "INSERT, UPDATE, and DELETE statements are not allowed
within a cursor declaration", but I'm thinking we may want to allow
things like COPY and EXPLAIN inside CTEs in the future, too, and
they'll presumably be treated similarly to DML.

"INSERT, UPDATE and DELETE" is quite long and "non-SELECT" is a bit
clumsy IMO. �But I don't really have anything better to offer, either.

Yeah, I don't feel good about "INSERT, UPDATE, and DELETE" because in
most of the relevant contexts the list might get longer if in the
future we allow things like EXPLAIN and COPY within CTEs. I think
"Non-SELECT statement" is reasonably clear, though; people might not
know which things are statements, but the message implies that SELECT
is one such thing, and not the one that's the problem, which should
get them pointed in the right direction.

Hmm, how about VALUES? Isn't that a statement on its own right, that
would similarly unaffected?

--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

#11Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#10)
Re: Writeable CTEs patch

On Mon, Feb 8, 2010 at 3:30 PM, Alvaro Herrera
<alvherre@commandprompt.com> wrote:

Robert Haas escribió:

On Mon, Feb 8, 2010 at 1:01 PM, Marko Tiikkaja
<marko.tiikkaja@cs.helsinki.fi> wrote:

Could we just write, e.g.
"non-SELECT statements are not allowed within a cursor declaration?"
Or we could say "INSERT, UPDATE, and DELETE statements are not allowed
within a cursor declaration", but I'm thinking we may want to allow
things like COPY and EXPLAIN inside CTEs in the future, too, and
they'll presumably be treated similarly to DML.

"INSERT, UPDATE and DELETE" is quite long and "non-SELECT" is a bit
clumsy IMO.  But I don't really have anything better to offer, either.

Yeah, I don't feel good about "INSERT, UPDATE, and DELETE" because in
most of the relevant contexts the list might get longer if in the
future we allow things like EXPLAIN and COPY within CTEs.  I think
"Non-SELECT statement" is reasonably clear, though; people might not
know which things are statements, but the message implies that SELECT
is one such thing, and not the one that's the problem, which should
get them pointed in the right direction.

Hmm, how about VALUES?  Isn't that a statement on its own right, that
would similarly unaffected?

Ouch. You're right, that's a problem. :-(

TABLE is a similar case.

...Robert

#12Marko Tiikkaja
marko.tiikkaja@cs.helsinki.fi
In reply to: Robert Haas (#6)
Re: Writeable CTEs patch

On 2010-02-08 18:42 +0200, Robert Haas wrote:

On Thu, Feb 4, 2010 at 11:57 AM, Marko Tiikkaja
<marko.tiikkaja@cs.helsinki.fi> wrote:

Here's an updated patch. Only changes from the previous patch are
fixing the above issue and a regression test for it.

- I'm not sure that canSetTag is the right name for the additional
argument to ExecInsert/ExecUpdate/ExecDelete. OTOH, I'm not sure it's
the wrong name either. But should we use something like
isTopLevelQuery?

I'm going to have to take back my previous statement; this doesn't make
a lot of sense in the case of DO ALSO rules (or multiple statements in a
DO INSTEAD RULE). Those will have canSetTag=false, but they will be at
the top level.

- It appears that we pull out all of the DML statements first and run
them in order, but I'm not sure that's the right thing to do.
Consider:

WITH x AS (INSERT ...), y AS (SELECT ...), z AS (INSERT ...) SELECT ...

I would assume we would do x, CCI, do y, do z, CCI, do main query, but
I don't think that's what this implements. The user might be
surprised to find out that y sees the effects of z.

I went ahead and implemented this, but there seems to be one small
problem: RECURSIVE. If there is a recursive query between those, it
might loop forever even if the top-level SELECT only wanted to see a few
rows from it. The docs already discourage writing recursive ctes like
that, but still this is a small caveat.

- It seems like the gram.y changes for common_table_expr might benefit
from some factoring; that is, create a production (or find a suitable
existing one) for "statements of the sort that can appear within
CTEs", and then use that in common_table_expr. Or maybe this doesn't
work; I haven't tried it.

This seems to work. I used PreparableStmt, but I'm not sure how good
idea that really is. Maybe I should create a new one?

Regards,
Marko Tiikkaja

#13Robert Haas
robertmhaas@gmail.com
In reply to: Marko Tiikkaja (#12)
Re: Writeable CTEs patch

On Tue, Feb 9, 2010 at 3:13 PM, Marko Tiikkaja
<marko.tiikkaja@cs.helsinki.fi> wrote:

On 2010-02-08 18:42 +0200, Robert Haas wrote:

On Thu, Feb 4, 2010 at 11:57 AM, Marko Tiikkaja
<marko.tiikkaja@cs.helsinki.fi> wrote:

Here's an updated patch.  Only changes from the previous patch are
fixing the above issue and a regression test for it.

- I'm not sure that canSetTag is the right name for the additional
argument to ExecInsert/ExecUpdate/ExecDelete.  OTOH, I'm not sure it's
the wrong name either.  But should we use something like
isTopLevelQuery?

I'm going to have to take back my previous statement; this doesn't make
a lot of sense in the case of DO ALSO rules (or multiple statements in a
DO INSTEAD RULE).  Those will have canSetTag=false, but they will be at
the top level.

Ah. OK.

- It appears that we pull out all of the DML statements first and run
them in order, but I'm not sure that's the right thing to do.
Consider:

WITH x AS (INSERT ...), y AS (SELECT ...), z AS (INSERT ...) SELECT ...

I would assume we would do x, CCI, do y, do z, CCI, do main query, but
I don't think that's what this implements.  The user might be
surprised to find out that y sees the effects of z.

I went ahead and implemented this, but there seems to be one small
problem: RECURSIVE.  If there is a recursive query between those, it
might loop forever even if the top-level SELECT only wanted to see a few
rows from it.  The docs already discourage writing recursive ctes like
that, but still this is a small caveat.

Doesn't seem like a big problem to me.

- It seems like the gram.y changes for common_table_expr might benefit
from some factoring; that is, create a production (or find a suitable
existing one) for "statements of the sort that can appear within
CTEs", and then use that in common_table_expr.  Or maybe this doesn't
work; I haven't tried it.

This seems to work.  I used PreparableStmt, but I'm not sure how good
idea that really is.  Maybe I should create a new one?

If it covers the same territory, I wouldn't duplicate it just for fun.
Someone might need to split it out in the future, but that's not a
reason to do it now.

...Robert

#14Marko Tiikkaja
marko.tiikkaja@cs.helsinki.fi
In reply to: Robert Haas (#6)
1 attachment(s)
Re: Writeable CTEs patch

On 2010-02-08 18:42 +0200, Robert Haas wrote:

On Thu, Feb 4, 2010 at 11:57 AM, Marko Tiikkaja
<marko.tiikkaja@cs.helsinki.fi> wrote:

On 2010-02-04 18:04 UTC+2, I wrote:

While working on the docs, I noticed one problem with the patch itself:
it doesn't handle multi-statement DO INSTEAD rules correctly. I'm going
to submit a fix for that later.

Here's an updated patch. Only changes from the previous patch are
fixing the above issue and a regression test for it.

- It appears that we pull out all of the DML statements first and run
them in order, but I'm not sure that's the right thing to do.
Consider:

WITH x AS (INSERT ...), y AS (SELECT ...), z AS (INSERT ...) SELECT ...

I would assume we would do x, CCI, do y, do z, CCI, do main query, but
I don't think that's what this implements. The user might be
surprised to find out that y sees the effects of z.

I've updated the patch according to what I said here:
http://archives.postgresql.org/pgsql-hackers/2010-02/msg00722.php

I haven't done any extensive testing, but it seems to work in the most
common cases.

Regards,
Marko Tiikkaja

Attachments:

dmlwith10.patchtext/plain; charset=iso-8859-1; name=dmlwith10.patchDownload
*** 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
***************
*** 947,953 **** ExecuteTruncate(TruncateStmt *stmt)
  		InitResultRelInfo(resultRelInfo,
  						  rel,
  						  0,	/* dummy rangetable index */
- 						  CMD_DELETE,	/* don't need any index info */
  						  0);
  		resultRelInfo++;
  	}
--- 947,952 ----
*** 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 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
***************
*** 157,165 **** 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;
  
--- 157,166 ----
  	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;
  
***************
*** 665,671 **** InitPlan(QueryDesc *queryDesc, int eflags)
  			InitResultRelInfo(resultRelInfo,
  							  resultRelation,
  							  resultRelationIndex,
- 							  operation,
  							  estate->es_instrument);
  			resultRelInfo++;
  		}
--- 666,671 ----
***************
*** 796,801 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 796,856 ----
  	 */
  	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
***************
*** 858,864 **** void
  InitResultRelInfo(ResultRelInfo *resultRelInfo,
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
- 				  CmdType operation,
  				  int instrument_options)
  {
  	/*
--- 913,918 ----
***************
*** 926,941 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
  	resultRelInfo->ri_ConstraintExprs = NULL;
  	resultRelInfo->ri_junkFilter = NULL;
  	resultRelInfo->ri_projectReturning = NULL;
- 
- 	/*
- 	 * If there are indices on the result relation, open them and save
- 	 * descriptors in the result relation info, so that we can add new index
- 	 * entries for the tuples we add/update.  We need not do this for a
- 	 * DELETE, however, since deletion doesn't affect indexes.
- 	 */
- 	if (resultRelationDesc->rd_rel->relhasindex &&
- 		operation != CMD_DELETE)
- 		ExecOpenIndices(resultRelInfo);
  }
  
  /*
--- 980,985 ----
***************
*** 991,1005 **** ExecGetTriggerResultRel(EState *estate, Oid relid)
  
  	/*
  	 * Make the new entry in the right context.  Currently, we don't need any
! 	 * index information in ResultRelInfos used only for triggers, so tell
! 	 * InitResultRelInfo it's a DELETE.
  	 */
  	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
  	rInfo = makeNode(ResultRelInfo);
  	InitResultRelInfo(rInfo,
  					  rel,
  					  0,		/* dummy rangetable index */
- 					  CMD_DELETE,
  					  estate->es_instrument);
  	estate->es_trig_target_relations =
  		lappend(estate->es_trig_target_relations, rInfo);
--- 1035,1047 ----
  
  	/*
  	 * Make the new entry in the right context.  Currently, we don't need any
! 	 * index information in ResultRelInfos used only for triggers.
  	 */
  	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
  	rInfo = makeNode(ResultRelInfo);
  	InitResultRelInfo(rInfo,
  					  rel,
  					  0,		/* dummy rangetable index */
  					  estate->es_instrument);
  	estate->es_trig_target_relations =
  		lappend(estate->es_trig_target_relations, rInfo);
***************
*** 1165,1170 **** ExecutePlan(EState *estate,
--- 1207,1213 ----
  {
  	TupleTableSlot *slot;
  	long		current_tuple_count;
+ 	ListCell	   *lc;
  
  	/*
  	 * initialize local variables
***************
*** 1177,1182 **** ExecutePlan(EState *estate,
--- 1220,1252 ----
  	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, NULL);
+ 		}
+ 	}
+ 
+ 	/*
  	 * Loop until we've processed the proper number of tuples from the plan.
  	 */
  	for (;;)
***************
*** 1957,1963 **** EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
  	 * ExecInitSubPlan expects to be able to find these entries.
  	 * Some of the SubPlans might not be used in the part of the plan tree
  	 * we intend to run, but since it's not easy to tell which, we just
! 	 * initialize them all.
  	 */
  	Assert(estate->es_subplanstates == NIL);
  	foreach(l, parentestate->es_plannedstmt->subplans)
--- 2027,2034 ----
  	 * ExecInitSubPlan expects to be able to find these entries.
  	 * Some of the SubPlans might not be used in the part of the plan tree
  	 * we intend to run, but since it's not easy to tell which, we just
! 	 * initialize them all.  However, we will never run ModifyTable nodes in
! 	 * EvalPlanQual() so don't initialize them.
  	 */
  	Assert(estate->es_subplanstates == NIL);
  	foreach(l, parentestate->es_plannedstmt->subplans)
***************
*** 1965,1971 **** EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
  		Plan	   *subplan = (Plan *) lfirst(l);
  		PlanState  *subplanstate;
  
! 		subplanstate = ExecInitNode(subplan, estate, 0);
  
  		estate->es_subplanstates = lappend(estate->es_subplanstates,
  										   subplanstate);
--- 2036,2046 ----
  		Plan	   *subplan = (Plan *) lfirst(l);
  		PlanState  *subplanstate;
  
! 		/* Don't initialize ModifyTable subplans. */
! 		if (IsA(subplan, ModifyTable))
! 			subplanstate = NULL;
! 		else
! 			subplanstate = ExecInitNode(subplan, estate, 0);
  
  		estate->es_subplanstates = lappend(estate->es_subplanstates,
  										   subplanstate);
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 158,164 **** ExecProcessReturning(ProjectionInfo *projectReturning,
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecInsert(TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EState *estate)
  {
--- 158,165 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecInsert(bool canSetTag,
! 		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EState *estate)
  {
***************
*** 240,246 **** ExecInsert(TupleTableSlot *slot,
  	newId = heap_insert(resultRelationDesc, tuple,
  						estate->es_output_cid, 0, NULL);
  
! 	(estate->es_processed)++;
  	estate->es_lastoid = newId;
  	setLastTid(&(tuple->t_self));
  
--- 241,249 ----
  	newId = heap_insert(resultRelationDesc, tuple,
  						estate->es_output_cid, 0, NULL);
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
! 
  	estate->es_lastoid = newId;
  	setLastTid(&(tuple->t_self));
  
***************
*** 274,280 **** ExecInsert(TupleTableSlot *slot,
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(ItemPointer tupleid,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
--- 277,284 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(bool canSetTag,
! 		   ItemPointer tupleid,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
***************
*** 356,362 **** ldelete:;
  			return NULL;
  	}
  
! 	(estate->es_processed)++;
  
  	/*
  	 * Note: Normally one would think that we have to delete index tuples
--- 360,367 ----
  			return NULL;
  	}
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
  
  	/*
  	 * Note: Normally one would think that we have to delete index tuples
***************
*** 417,423 **** ldelete:;
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(ItemPointer tupleid,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
--- 422,429 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(bool canSetTag,
! 		   ItemPointer tupleid,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
***************
*** 546,552 **** lreplace:;
  			return NULL;
  	}
  
! 	(estate->es_processed)++;
  
  	/*
  	 * Note: instead of having to update the old index tuples associated with
--- 552,559 ----
  			return NULL;
  	}
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
  
  	/*
  	 * Note: instead of having to update the old index tuples associated with
***************
*** 593,607 **** 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");
--- 600,614 ----
  	{
  		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");
***************
*** 619,633 **** 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");
--- 626,640 ----
  	{
  		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");
***************
*** 649,654 **** ExecModifyTable(ModifyTableState *node)
--- 656,662 ----
  	EState *estate = node->ps.state;
  	CmdType operation = node->operation;
  	PlanState *subplanstate;
+ 	ResultRelInfo *resultRelInfo;
  	JunkFilter *junkfilter;
  	TupleTableSlot *slot;
  	TupleTableSlot *planSlot;
***************
*** 664,680 **** 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
--- 672,681 ----
  		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
***************
*** 690,698 **** 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;
  			}
--- 691,699 ----
  			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;
  			}
***************
*** 730,747 **** ExecModifyTable(ModifyTableState *node)
  			if (operation != CMD_DELETE)
  				slot = ExecFilterJunk(junkfilter, slot);
  		}
  
  		switch (operation)
  		{
  			case CMD_INSERT:
! 				slot = ExecInsert(slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(tupleid, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(tupleid, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
--- 731,755 ----
  			if (operation != CMD_DELETE)
  				slot = ExecFilterJunk(junkfilter, slot);
  		}
+ 	
+ 		/*
+ 	 	 * es_result_relation_info must point to the currently active result
+ 	 	 * relation.  We want it to be NULL whenever we're not within
+ 	 	 * ModifyTable, though.
+ 		 */
+ 		estate->es_result_relation_info = resultRelInfo;
  
  		switch (operation)
  		{
  			case CMD_INSERT:
! 				slot = ExecInsert(node->canSetTag, slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(node->canSetTag, tupleid, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(node->canSetTag, tupleid, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
***************
*** 809,833 **** 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;
--- 817,856 ----
  	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
  	mtstate->mt_nplans = nplans;
  	mtstate->operation = operation;
+ 	mtstate->canSetTag = node->canSetTag;
+ 	mtstate->resultRelIndex = node->resultRelIndex;
+ 	mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
+ 
  	/* set up epqstate with dummy subplan pointer for the moment */
  	EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, node->epqParam);
  	mtstate->fireBSTriggers = true;
  
  	/*
  	 * call ExecInitNode on each of the plans to be executed and save the
  	 * results into the array "mt_plans".  Note we *must* set
  	 * estate->es_result_relation_info correctly while we initialize each
  	 * sub-plan; ExecContextForcesOids depends on that!
  	 */
  	i = 0;
+ 	resultRelInfo = mtstate->resultRelInfo;
  	foreach(l, node->plans)
  	{
  		subplan = (Plan *) lfirst(l);
+ 
+ 		/*
+ 		 * If there are indices on the result relation, open them and save
+ 		 * descriptors in the result relation info, so that we can add new index
+ 		 * entries for the tuples we add/update.  We need not do this for a
+ 		 * DELETE, however, since deletion doesn't affect indexes.
+ 		 */
+ 		if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+ 			operation != CMD_DELETE)
+ 			ExecOpenIndices(resultRelInfo);
+ 
+ 		estate->es_result_relation_info = resultRelInfo;
  		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
! 
! 		resultRelInfo++;
  		i++;
  	}
  	estate->es_result_relation_info = NULL;
***************
*** 864,871 **** 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);
--- 887,893 ----
  		/*
  		 * Build a projection for each result rel.
  		 */
! 		resultRelInfo = mtstate->resultRelInfo;
  		foreach(l, node->returningLists)
  		{
  			List	   *rlist = (List *) lfirst(l);
***************
*** 964,970 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  
  		if (junk_filter_needed)
  		{
! 			resultRelInfo = estate->es_result_relations;
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
--- 986,992 ----
  
  		if (junk_filter_needed)
  		{
! 			resultRelInfo = mtstate->resultRelInfo;
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
***************
*** 993,999 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  		else
  		{
  			if (operation == CMD_INSERT)
! 				ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc,
  									subplan->targetlist);
  		}
  	}
--- 1015,1021 ----
  		else
  		{
  			if (operation == CMD_INSERT)
! 				ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
  									subplan->targetlist);
  		}
  	}
*** a/src/backend/executor/nodeSubplan.c
--- b/src/backend/executor/nodeSubplan.c
***************
*** 668,673 **** ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
--- 668,675 ----
  	sstate->planstate = (PlanState *) list_nth(estate->es_subplanstates,
  											   subplan->plan_id - 1);
  
+ 	Assert(sstate->planstate != NULL);
+ 
  	/* Initialize subexpressions */
  	sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
  	sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent);
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 84,89 **** _copyPlannedStmt(PlannedStmt *from)
--- 84,90 ----
  	COPY_NODE_FIELD(resultRelations);
  	COPY_NODE_FIELD(utilityStmt);
  	COPY_NODE_FIELD(intoClause);
+ 	COPY_SCALAR_FIELD(hasDmlWith);
  	COPY_NODE_FIELD(subplans);
  	COPY_BITMAPSET_FIELD(rewindPlanIDs);
  	COPY_NODE_FIELD(rowMarks);
***************
*** 171,177 **** _copyModifyTable(ModifyTable *from)
--- 172,180 ----
  	 * copy remainder of node
  	 */
  	COPY_SCALAR_FIELD(operation);
+ 	COPY_SCALAR_FIELD(canSetTag);
  	COPY_NODE_FIELD(resultRelations);
+ 	COPY_SCALAR_FIELD(resultRelIndex);
  	COPY_NODE_FIELD(plans);
  	COPY_NODE_FIELD(returningLists);
  	COPY_NODE_FIELD(rowMarks);
***************
*** 2260,2265 **** _copyInsertStmt(InsertStmt *from)
--- 2263,2269 ----
  	COPY_NODE_FIELD(cols);
  	COPY_NODE_FIELD(selectStmt);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
***************
*** 2273,2278 **** _copyDeleteStmt(DeleteStmt *from)
--- 2277,2283 ----
  	COPY_NODE_FIELD(usingClause);
  	COPY_NODE_FIELD(whereClause);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
***************
*** 2287,2292 **** _copyUpdateStmt(UpdateStmt *from)
--- 2292,2298 ----
  	COPY_NODE_FIELD(whereClause);
  	COPY_NODE_FIELD(fromClause);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 888,893 **** _equalInsertStmt(InsertStmt *a, InsertStmt *b)
--- 888,894 ----
  	COMPARE_NODE_FIELD(cols);
  	COMPARE_NODE_FIELD(selectStmt);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
***************
*** 899,904 **** _equalDeleteStmt(DeleteStmt *a, DeleteStmt *b)
--- 900,906 ----
  	COMPARE_NODE_FIELD(usingClause);
  	COMPARE_NODE_FIELD(whereClause);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
***************
*** 911,916 **** _equalUpdateStmt(UpdateStmt *a, UpdateStmt *b)
--- 913,919 ----
  	COMPARE_NODE_FIELD(whereClause);
  	COMPARE_NODE_FIELD(fromClause);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 2394,2399 **** bool
--- 2394,2449 ----
  					return true;
  			}
  			break;
+ 		case T_InsertStmt:
+ 			{
+ 				InsertStmt *stmt = (InsertStmt *) node;
+ 
+ 				if (walker(stmt->relation, context))
+ 					return true;
+ 				if (walker(stmt->cols, context))
+ 					return true;
+ 				if (walker(stmt->selectStmt, context))
+ 					return true;
+ 				if (walker(stmt->returningList, context))
+ 					return true;
+ 				if (walker(stmt->withClause, context))
+ 					return true;
+ 			}
+ 			break;
+ 		case T_UpdateStmt:
+ 			{
+ 				UpdateStmt *stmt = (UpdateStmt *) node;
+ 
+ 				if (walker(stmt->relation, context))
+ 					return true;
+ 				if (walker(stmt->targetList, context))
+ 					return true;
+ 				if (walker(stmt->whereClause, context))
+ 					return true;
+ 				if (walker(stmt->fromClause, context))
+ 					return true;
+ 				if (walker(stmt->returningList, context))
+ 					return true;
+ 				if (walker(stmt->withClause, context))
+ 					return true;
+ 			}
+ 			break;
+ 		case T_DeleteStmt:
+ 			{
+ 				DeleteStmt *stmt = (DeleteStmt *) node;
+ 
+ 				if (walker(stmt->relation, context))
+ 					return true;
+ 				if (walker(stmt->usingClause, context))
+ 					return true;
+ 				if (walker(stmt->whereClause, context))
+ 					return true;
+ 				if (walker(stmt->returningList, context))
+ 					return true;
+ 				if (walker(stmt->withClause, context))
+ 					return true;
+ 			}
+ 			break;
  		case T_A_Expr:
  			{
  				A_Expr	   *expr = (A_Expr *) node;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 327,332 **** _outModifyTable(StringInfo str, ModifyTable *node)
--- 327,333 ----
  
  	WRITE_ENUM_FIELD(operation, CmdType);
  	WRITE_NODE_FIELD(resultRelations);
+ 	WRITE_INT_FIELD(resultRelIndex);
  	WRITE_NODE_FIELD(plans);
  	WRITE_NODE_FIELD(returningLists);
  	WRITE_NODE_FIELD(rowMarks);
***************
*** 1533,1538 **** _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
--- 1534,1540 ----
  	WRITE_NODE_FIELD(finalrowmarks);
  	WRITE_NODE_FIELD(relationOids);
  	WRITE_NODE_FIELD(invalItems);
+ 	WRITE_NODE_FIELD(resultRelations);
  	WRITE_UINT_FIELD(lastPHId);
  	WRITE_BOOL_FIELD(transientPlan);
  }
***************
*** 1548,1554 **** _outPlannerInfo(StringInfo str, PlannerInfo *node)
  	WRITE_UINT_FIELD(query_level);
  	WRITE_NODE_FIELD(join_rel_list);
  	WRITE_INT_FIELD(join_cur_level);
- 	WRITE_NODE_FIELD(resultRelations);
  	WRITE_NODE_FIELD(init_plans);
  	WRITE_NODE_FIELD(cte_plan_ids);
  	WRITE_NODE_FIELD(eq_classes);
--- 1550,1555 ----
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 3754,3760 **** make_result(PlannerInfo *root,
   * to make it look better sometime.
   */
  ModifyTable *
! make_modifytable(CmdType operation, List *resultRelations,
  				 List *subplans, List *returningLists,
  				 List *rowMarks, int epqParam)
  {
--- 3754,3761 ----
   * to make it look better sometime.
   */
  ModifyTable *
! make_modifytable(CmdType operation, bool canSetTag,
! 				 List *resultRelations,
  				 List *subplans, List *returningLists,
  				 List *rowMarks, int epqParam)
  {
***************
*** 3804,3809 **** make_modifytable(CmdType operation, List *resultRelations,
--- 3805,3811 ----
  		node->plan.targetlist = NIL;
  
  	node->operation = operation;
+ 	node->canSetTag = canSetTag;
  	node->resultRelations = resultRelations;
  	node->plans = subplans;
  	node->returningLists = returningLists;
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 160,165 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
--- 160,167 ----
  	glob->finalrowmarks = NIL;
  	glob->relationOids = NIL;
  	glob->invalItems = NIL;
+ 	glob->hasDmlWith = 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->hasDmlWith = glob->hasDmlWith;
  	result->subplans = glob->subplans;
  	result->rewindPlanIDs = glob->rewindPlanIDs;
  	result->rowMarks = glob->finalrowmarks;
***************
*** 541,547 **** subquery_planner(PlannerGlobal *glob, Query *parse,
  				rowMarks = root->rowMarks;
  
  			plan = (Plan *) make_modifytable(parse->commandType,
! 											 copyObject(root->resultRelations),
  											 list_make1(plan),
  											 returningLists,
  											 rowMarks,
--- 544,551 ----
  				rowMarks = root->rowMarks;
  
  			plan = (Plan *) make_modifytable(parse->commandType,
! 											 parse->canSetTag,
! 											 list_make1_int(parse->resultRelation),
  											 list_make1(plan),
  											 returningLists,
  											 rowMarks,
***************
*** 706,718 **** inheritance_planner(PlannerInfo *root)
  	Query	   *parse = root->parse;
  	int			parentRTindex = parse->resultRelation;
  	List	   *subplans = NIL;
- 	List	   *resultRelations = NIL;
  	List	   *returningLists = NIL;
  	List	   *rtable = NIL;
  	List	   *rowMarks;
  	List	   *tlist;
  	PlannerInfo subroot;
  	ListCell   *l;
  
  	foreach(l, root->append_rel_list)
  	{
--- 710,722 ----
  	Query	   *parse = root->parse;
  	int			parentRTindex = parse->resultRelation;
  	List	   *subplans = NIL;
  	List	   *returningLists = NIL;
  	List	   *rtable = NIL;
  	List	   *rowMarks;
  	List	   *tlist;
  	PlannerInfo subroot;
  	ListCell   *l;
+ 	List	   *resultRelations = NIL;
  
  	foreach(l, root->append_rel_list)
  	{
***************
*** 772,779 **** inheritance_planner(PlannerInfo *root)
  		}
  	}
  
- 	root->resultRelations = resultRelations;
- 
  	/* Mark result as unordered (probably unnecessary) */
  	root->query_pathkeys = NIL;
  
--- 776,781 ----
***************
*** 783,789 **** inheritance_planner(PlannerInfo *root)
  	 */
  	if (subplans == NIL)
  	{
- 		root->resultRelations = list_make1_int(parentRTindex);
  		/* although dummy, it must have a valid tlist for executor */
  		tlist = preprocess_targetlist(root, parse->targetList);
  		return (Plan *) make_result(root,
--- 785,790 ----
***************
*** 818,824 **** inheritance_planner(PlannerInfo *root)
  
  	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
  	return (Plan *) make_modifytable(parse->commandType,
! 									 copyObject(root->resultRelations),
  									 subplans, 
  									 returningLists,
  									 rowMarks,
--- 819,826 ----
  
  	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
  	return (Plan *) make_modifytable(parse->commandType,
! 									 parse->canSetTag,
! 									 resultRelations,
  									 subplans, 
  									 returningLists,
  									 rowMarks,
***************
*** 1668,1679 **** grouping_planner(PlannerInfo *root, double tuple_fraction)
  										  count_est);
  	}
  
- 	/* Compute result-relations list if needed */
- 	if (parse->resultRelation)
- 		root->resultRelations = list_make1_int(parse->resultRelation);
- 	else
- 		root->resultRelations = NIL;
- 
  	/*
  	 * Return the actual output ordering in query_pathkeys for possible use by
  	 * an outer query level.
--- 1670,1675 ----
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 520,525 **** set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
--- 520,529 ----
  											  (Plan *) lfirst(l),
  											  rtoffset);
  				}
+ 
+ 				splan->resultRelIndex = list_length(glob->resultRelations);
+ 				glob->resultRelations = list_concat(glob->resultRelations,
+ 													splan->resultRelations);
  			}
  			break;
  		case T_Append:
*** a/src/backend/optimizer/plan/subselect.c
--- b/src/backend/optimizer/plan/subselect.c
***************
*** 873,888 **** SS_process_ctes(PlannerInfo *root)
  		Bitmapset  *tmpset;
  		int			paramid;
  		Param	   *prm;
  
  		/*
! 		 * Ignore CTEs that are not actually referenced anywhere.
  		 */
! 		if (cte->cterefcount == 0)
  		{
  			/* Make a dummy entry in cte_plan_ids */
  			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
  			continue;
  		}
  
  		/*
  		 * Copy the source Query node.	Probably not necessary, but let's keep
--- 873,906 ----
  		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")));
+ 
+ 			if (root->query_level > 1)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("DML WITH is only allowed at the top level")));
+ 
+ 			root->glob->hasDmlWith = true;
+ 		}
  
  		/*
  		 * Copy the source Query node.	Probably not necessary, but let's keep
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 290,295 **** transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
--- 290,302 ----
  
  	qry->distinctClause = NIL;
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * The USING clause is non-standard SQL syntax, and is equivalent in
  	 * functionality to the FROM list that can be specified for UPDATE. The
***************
*** 342,347 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 349,361 ----
  	qry->commandType = CMD_INSERT;
  	pstate->p_is_insert = true;
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
  	 * VALUES list, or general SELECT input.  We special-case VALUES, both for
***************
*** 366,373 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
  		pstate->p_relnamespace = NIL;
  		sub_varnamespace = pstate->p_varnamespace;
  		pstate->p_varnamespace = NIL;
- 		/* There can't be any outer WITH to worry about */
- 		Assert(pstate->p_ctenamespace == NIL);
  	}
  	else
  	{
--- 380,385 ----
***************
*** 1735,1740 **** transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
--- 1747,1759 ----
  										 true,
  										 ACL_UPDATE);
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * the FROM clause is non-standard SQL syntax. We used to be able to do
  	 * this with REPLACE in POSTQUEL so we keep the feature.
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 429,435 **** static TypeName *TableFuncTypeName(List *columns);
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
! %type <with> 	with_clause
  %type <list>	cte_list
  
  %type <list>	window_clause window_definition_list opt_partition_clause
--- 429,435 ----
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
! %type <with> 	with_clause opt_with_clause
  %type <list>	cte_list
  
  %type <list>	window_clause window_definition_list opt_partition_clause
***************
*** 7071,7081 **** DeallocateStmt: DEALLOCATE name
   *****************************************************************************/
  
  InsertStmt:
! 			INSERT INTO qualified_name insert_rest returning_clause
  				{
! 					$4->relation = $3;
! 					$4->returningList = $5;
! 					$$ = (Node *) $4;
  				}
  		;
  
--- 7071,7082 ----
   *****************************************************************************/
  
  InsertStmt:
! 			opt_with_clause INSERT INTO qualified_name insert_rest returning_clause
  				{
! 					$5->relation = $4;
! 					$5->returningList = $6;
! 					$5->withClause = $1;
! 					$$ = (Node *) $5;
  				}
  		;
  
***************
*** 7131,7144 **** returning_clause:
   *
   *****************************************************************************/
  
! DeleteStmt: DELETE_P FROM relation_expr_opt_alias
  			using_clause where_or_current_clause returning_clause
  				{
  					DeleteStmt *n = makeNode(DeleteStmt);
! 					n->relation = $3;
! 					n->usingClause = $4;
! 					n->whereClause = $5;
! 					n->returningList = $6;
  					$$ = (Node *)n;
  				}
  		;
--- 7132,7146 ----
   *
   *****************************************************************************/
  
! DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias
  			using_clause where_or_current_clause returning_clause
  				{
  					DeleteStmt *n = makeNode(DeleteStmt);
! 					n->relation = $4;
! 					n->usingClause = $5;
! 					n->whereClause = $6;
! 					n->returningList = $7;
! 					n->withClause = $1;
  					$$ = (Node *)n;
  				}
  		;
***************
*** 7193,7210 **** opt_nowait:	NOWAIT							{ $$ = TRUE; }
   *
   *****************************************************************************/
  
! UpdateStmt: UPDATE relation_expr_opt_alias
  			SET set_clause_list
  			from_clause
  			where_or_current_clause
  			returning_clause
  				{
  					UpdateStmt *n = makeNode(UpdateStmt);
! 					n->relation = $2;
! 					n->targetList = $4;
! 					n->fromClause = $5;
! 					n->whereClause = $6;
! 					n->returningList = $7;
  					$$ = (Node *)n;
  				}
  		;
--- 7195,7213 ----
   *
   *****************************************************************************/
  
! UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
  			SET set_clause_list
  			from_clause
  			where_or_current_clause
  			returning_clause
  				{
  					UpdateStmt *n = makeNode(UpdateStmt);
! 					n->relation = $3;
! 					n->targetList = $5;
! 					n->fromClause = $6;
! 					n->whereClause = $7;
! 					n->returningList = $8;
! 					n->withClause = $1;
  					$$ = (Node *)n;
  				}
  		;
***************
*** 7530,7546 **** with_clause:
  			}
  		;
  
  cte_list:
  		common_table_expr						{ $$ = list_make1($1); }
  		| 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;
  			}
--- 7533,7553 ----
  			}
  		;
  
+ opt_with_clause:
+ 		with_clause								{ $$ = $1; }
+ 		| /*EMPTY*/								{ $$ = NULL; }
+ 
  cte_list:
  		common_table_expr						{ $$ = list_make1($1); }
  		| 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;
***************
*** 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,
--- 595,613 ----
  		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
***************
*** 314,323 **** markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 				ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
  									   attnum);
  				if (ste == NULL || ste->resjunk)
  					elog(ERROR, "subquery %s does not have attribute %d",
--- 314,333 ----
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
+ 				List		*cteList;
+ 				Query		*ctequery;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 
! 				ctequery = (Query *) cte->ctequery;
! 
! 				if (ctequery->commandType == CMD_SELECT)
! 					cteList = ctequery->targetList;
! 				else
! 					cteList = ctequery->returningList;
! 
! 				ste = get_tle_by_resno(cteList,
  									   attnum);
  				if (ste == NULL || ste->resjunk)
  					elog(ERROR, "subquery %s does not have attribute %d",
***************
*** 1345,1355 **** expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 				ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
! 									   attnum);
  				if (ste == NULL || ste->resjunk)
  					elog(ERROR, "subquery %s does not have attribute %d",
  						 rte->eref->aliasname, attnum);
--- 1355,1374 ----
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
+ 				List		*cteList;
+ 				Query		*ctequery;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 
! 				ctequery = (Query *) cte->ctequery;
! 
! 				if (ctequery->commandType == CMD_SELECT)
! 					cteList = ctequery->targetList;
! 				else
! 					cteList = ctequery->returningList;
! 
! 				ste = get_tle_by_resno(cteList, attnum);
  				if (ste == NULL || ste->resjunk)
  					elog(ERROR, "subquery %s does not have attribute %d",
  						 rte->eref->aliasname, attnum);
***************
*** 1372,1378 **** expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
  						 levelsup++)
  						pstate = pstate->parentParseState;
  					mypstate.parentParseState = pstate;
! 					mypstate.p_rtable = ((Query *) cte->ctequery)->rtable;
  					/* don't bother filling the rest of the fake pstate */
  
  					return expandRecordVariable(&mypstate, (Var *) expr, 0);
--- 1391,1397 ----
  						 levelsup++)
  						pstate = pstate->parentParseState;
  					mypstate.parentParseState = pstate;
! 					mypstate.p_rtable = ctequery->rtable;
  					/* don't bother filling the rest of the fake pstate */
  
  					return expandRecordVariable(&mypstate, (Var *) expr, 0);
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 1632,1637 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1632,1641 ----
  	bool		returning = false;
  	Query	   *qual_product = NULL;
  	List	   *rewritten = NIL;
+ 	ListCell	*lc1;
+ 	CommonTableExpr	*cte;
+ 	Query		*ctequery;
+ 	List		*newstuff;
  
  	/*
  	 * If the statement is an update, insert or delete - fire rules on it.
***************
*** 1749,1755 **** RewriteQuery(Query *parsetree, List *rewrite_events)
  				foreach(n, product_queries)
  				{
  					Query	   *pt = (Query *) lfirst(n);
- 					List	   *newstuff;
  
  					newstuff = RewriteQuery(pt, rewrite_events);
  					rewritten = list_concat(rewritten, newstuff);
--- 1753,1758 ----
***************
*** 1804,1809 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1807,1877 ----
  	}
  
  	/*
+ 	 * 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
***************
*** 293,298 **** ChoosePortalStrategy(List *stmts)
--- 293,299 ----
  			if (pstmt->canSetTag)
  			{
  				if (pstmt->commandType == CMD_SELECT &&
+ 					!pstmt->hasDmlWith &&
  					pstmt->utilityStmt == NULL &&
  					pstmt->intoClause == NULL)
  					return PORTAL_ONE_SELECT;
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 3980,3988 **** 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",
--- 3980,3995 ----
  				}
  				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
***************
*** 160,166 **** extern void ExecutorRewind(QueryDesc *queryDesc);
  extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
- 				  CmdType operation,
  				  int instrument_options);
  extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
  extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
--- 160,165 ----
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 376,381 **** typedef struct EState
--- 376,382 ----
  
  	List	   *es_exprcontexts;	/* List of ExprContexts within EState */
  
+ 	List	   *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
***************
*** 850,856 **** 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? */
--- 850,856 ----
  	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? */
***************
*** 880,885 **** typedef struct InsertStmt
--- 880,886 ----
  	List	   *cols;			/* optional: names of the target columns */
  	Node	   *selectStmt;		/* the source SELECT/VALUES, or NULL */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } InsertStmt;
  
  /* ----------------------
***************
*** 893,898 **** typedef struct DeleteStmt
--- 894,900 ----
  	List	   *usingClause;	/* optional using clause for more tables */
  	Node	   *whereClause;	/* qualifications */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } DeleteStmt;
  
  /* ----------------------
***************
*** 907,912 **** typedef struct UpdateStmt
--- 909,915 ----
  	Node	   *whereClause;	/* qualifications */
  	List	   *fromClause;		/* optional from clause for more tables */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } UpdateStmt;
  
  /* ----------------------
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 55,60 **** typedef struct PlannedStmt
--- 55,62 ----
  
  	IntoClause *intoClause;		/* target for SELECT INTO / CREATE TABLE AS */
  
+ 	bool		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
***************
*** 77,83 **** extern SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
  		   long numGroups, double outputRows);
  extern Result *make_result(PlannerInfo *root, List *tlist,
  			Node *resconstantqual, Plan *subplan);
! extern ModifyTable *make_modifytable(CmdType operation, List *resultRelations,
  									 List *subplans, List *returningLists,
  									 List *rowMarks, int epqParam);
  extern bool is_projection_capable_plan(Plan *plan);
--- 77,84 ----
  		   long numGroups, double outputRows);
  extern Result *make_result(PlannerInfo *root, List *tlist,
  			Node *resconstantqual, Plan *subplan);
! extern ModifyTable *make_modifytable(CmdType operation, bool canSetTag,
! 									 List *resultRelations,
  									 List *subplans, List *returningLists,
  									 List *rowMarks, int epqParam);
  extern bool is_projection_capable_plan(Plan *plan);
*** a/src/test/regress/expected/with.out
--- b/src/test/regress/expected/with.out
***************
*** 1026,1028 **** SELECT * FROM t;
--- 1026,1220 ----
   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;
+ -- 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
+ -- 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
***************
*** 500,502 **** WITH RECURSIVE t(j) AS (
--- 500,623 ----
      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;
+ 
+ -- 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;
#15Marko Tiikkaja
marko.tiikkaja@cs.helsinki.fi
In reply to: Robert Haas (#6)
1 attachment(s)
Re: Writeable CTEs patch

On 2010-02-08 18:42 +0200, Robert Haas wrote:

On Thu, Feb 4, 2010 at 11:57 AM, Marko Tiikkaja
<marko.tiikkaja@cs.helsinki.fi> wrote:

On 2010-02-04 18:04 UTC+2, I wrote:

While working on the docs, I noticed one problem with the patch itself:
it doesn't handle multi-statement DO INSTEAD rules correctly. I'm going
to submit a fix for that later.

Here's an updated patch. Only changes from the previous patch are
fixing the above issue and a regression test for it.

- It appears that we pull out all of the DML statements first and run
them in order, but I'm not sure that's the right thing to do.
Consider:

WITH x AS (INSERT ...), y AS (SELECT ...), z AS (INSERT ...) SELECT ...

I would assume we would do x, CCI, do y, do z, CCI, do main query, but
I don't think that's what this implements. The user might be
surprised to find out that y sees the effects of z.

I've updated the patch according to what I said here:
http://archives.postgresql.org/pgsql-hackers/2010-02/msg00722.php

I haven't done any extensive testing, but it seems to work in the most
common cases.

Regards,
Marko Tiikkaja

Attachments:

dmlwith10.patchtext/plain; charset=iso-8859-1; name=dmlwith10.patchDownload
*** 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
***************
*** 947,953 **** ExecuteTruncate(TruncateStmt *stmt)
  		InitResultRelInfo(resultRelInfo,
  						  rel,
  						  0,	/* dummy rangetable index */
- 						  CMD_DELETE,	/* don't need any index info */
  						  0);
  		resultRelInfo++;
  	}
--- 947,952 ----
*** 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 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
***************
*** 157,165 **** 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;
  
--- 157,166 ----
  	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;
  
***************
*** 665,671 **** InitPlan(QueryDesc *queryDesc, int eflags)
  			InitResultRelInfo(resultRelInfo,
  							  resultRelation,
  							  resultRelationIndex,
- 							  operation,
  							  estate->es_instrument);
  			resultRelInfo++;
  		}
--- 666,671 ----
***************
*** 796,801 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 796,856 ----
  	 */
  	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
***************
*** 858,864 **** void
  InitResultRelInfo(ResultRelInfo *resultRelInfo,
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
- 				  CmdType operation,
  				  int instrument_options)
  {
  	/*
--- 913,918 ----
***************
*** 926,941 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
  	resultRelInfo->ri_ConstraintExprs = NULL;
  	resultRelInfo->ri_junkFilter = NULL;
  	resultRelInfo->ri_projectReturning = NULL;
- 
- 	/*
- 	 * If there are indices on the result relation, open them and save
- 	 * descriptors in the result relation info, so that we can add new index
- 	 * entries for the tuples we add/update.  We need not do this for a
- 	 * DELETE, however, since deletion doesn't affect indexes.
- 	 */
- 	if (resultRelationDesc->rd_rel->relhasindex &&
- 		operation != CMD_DELETE)
- 		ExecOpenIndices(resultRelInfo);
  }
  
  /*
--- 980,985 ----
***************
*** 991,1005 **** ExecGetTriggerResultRel(EState *estate, Oid relid)
  
  	/*
  	 * Make the new entry in the right context.  Currently, we don't need any
! 	 * index information in ResultRelInfos used only for triggers, so tell
! 	 * InitResultRelInfo it's a DELETE.
  	 */
  	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
  	rInfo = makeNode(ResultRelInfo);
  	InitResultRelInfo(rInfo,
  					  rel,
  					  0,		/* dummy rangetable index */
- 					  CMD_DELETE,
  					  estate->es_instrument);
  	estate->es_trig_target_relations =
  		lappend(estate->es_trig_target_relations, rInfo);
--- 1035,1047 ----
  
  	/*
  	 * Make the new entry in the right context.  Currently, we don't need any
! 	 * index information in ResultRelInfos used only for triggers.
  	 */
  	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
  	rInfo = makeNode(ResultRelInfo);
  	InitResultRelInfo(rInfo,
  					  rel,
  					  0,		/* dummy rangetable index */
  					  estate->es_instrument);
  	estate->es_trig_target_relations =
  		lappend(estate->es_trig_target_relations, rInfo);
***************
*** 1165,1170 **** ExecutePlan(EState *estate,
--- 1207,1213 ----
  {
  	TupleTableSlot *slot;
  	long		current_tuple_count;
+ 	ListCell	   *lc;
  
  	/*
  	 * initialize local variables
***************
*** 1177,1182 **** ExecutePlan(EState *estate,
--- 1220,1252 ----
  	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, NULL);
+ 		}
+ 	}
+ 
+ 	/*
  	 * Loop until we've processed the proper number of tuples from the plan.
  	 */
  	for (;;)
***************
*** 1957,1963 **** EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
  	 * ExecInitSubPlan expects to be able to find these entries.
  	 * Some of the SubPlans might not be used in the part of the plan tree
  	 * we intend to run, but since it's not easy to tell which, we just
! 	 * initialize them all.
  	 */
  	Assert(estate->es_subplanstates == NIL);
  	foreach(l, parentestate->es_plannedstmt->subplans)
--- 2027,2034 ----
  	 * ExecInitSubPlan expects to be able to find these entries.
  	 * Some of the SubPlans might not be used in the part of the plan tree
  	 * we intend to run, but since it's not easy to tell which, we just
! 	 * initialize them all.  However, we will never run ModifyTable nodes in
! 	 * EvalPlanQual() so don't initialize them.
  	 */
  	Assert(estate->es_subplanstates == NIL);
  	foreach(l, parentestate->es_plannedstmt->subplans)
***************
*** 1965,1971 **** EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
  		Plan	   *subplan = (Plan *) lfirst(l);
  		PlanState  *subplanstate;
  
! 		subplanstate = ExecInitNode(subplan, estate, 0);
  
  		estate->es_subplanstates = lappend(estate->es_subplanstates,
  										   subplanstate);
--- 2036,2046 ----
  		Plan	   *subplan = (Plan *) lfirst(l);
  		PlanState  *subplanstate;
  
! 		/* Don't initialize ModifyTable subplans. */
! 		if (IsA(subplan, ModifyTable))
! 			subplanstate = NULL;
! 		else
! 			subplanstate = ExecInitNode(subplan, estate, 0);
  
  		estate->es_subplanstates = lappend(estate->es_subplanstates,
  										   subplanstate);
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 158,164 **** ExecProcessReturning(ProjectionInfo *projectReturning,
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecInsert(TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EState *estate)
  {
--- 158,165 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecInsert(bool canSetTag,
! 		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EState *estate)
  {
***************
*** 240,246 **** ExecInsert(TupleTableSlot *slot,
  	newId = heap_insert(resultRelationDesc, tuple,
  						estate->es_output_cid, 0, NULL);
  
! 	(estate->es_processed)++;
  	estate->es_lastoid = newId;
  	setLastTid(&(tuple->t_self));
  
--- 241,249 ----
  	newId = heap_insert(resultRelationDesc, tuple,
  						estate->es_output_cid, 0, NULL);
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
! 
  	estate->es_lastoid = newId;
  	setLastTid(&(tuple->t_self));
  
***************
*** 274,280 **** ExecInsert(TupleTableSlot *slot,
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(ItemPointer tupleid,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
--- 277,284 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(bool canSetTag,
! 		   ItemPointer tupleid,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
***************
*** 356,362 **** ldelete:;
  			return NULL;
  	}
  
! 	(estate->es_processed)++;
  
  	/*
  	 * Note: Normally one would think that we have to delete index tuples
--- 360,367 ----
  			return NULL;
  	}
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
  
  	/*
  	 * Note: Normally one would think that we have to delete index tuples
***************
*** 417,423 **** ldelete:;
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(ItemPointer tupleid,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
--- 422,429 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(bool canSetTag,
! 		   ItemPointer tupleid,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
***************
*** 546,552 **** lreplace:;
  			return NULL;
  	}
  
! 	(estate->es_processed)++;
  
  	/*
  	 * Note: instead of having to update the old index tuples associated with
--- 552,559 ----
  			return NULL;
  	}
  
! 	if (canSetTag)
! 		(estate->es_processed)++;
  
  	/*
  	 * Note: instead of having to update the old index tuples associated with
***************
*** 593,607 **** 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");
--- 600,614 ----
  	{
  		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");
***************
*** 619,633 **** 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");
--- 626,640 ----
  	{
  		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");
***************
*** 649,654 **** ExecModifyTable(ModifyTableState *node)
--- 656,662 ----
  	EState *estate = node->ps.state;
  	CmdType operation = node->operation;
  	PlanState *subplanstate;
+ 	ResultRelInfo *resultRelInfo;
  	JunkFilter *junkfilter;
  	TupleTableSlot *slot;
  	TupleTableSlot *planSlot;
***************
*** 664,680 **** 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
--- 672,681 ----
  		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
***************
*** 690,698 **** 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;
  			}
--- 691,699 ----
  			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;
  			}
***************
*** 730,747 **** ExecModifyTable(ModifyTableState *node)
  			if (operation != CMD_DELETE)
  				slot = ExecFilterJunk(junkfilter, slot);
  		}
  
  		switch (operation)
  		{
  			case CMD_INSERT:
! 				slot = ExecInsert(slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(tupleid, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(tupleid, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
--- 731,755 ----
  			if (operation != CMD_DELETE)
  				slot = ExecFilterJunk(junkfilter, slot);
  		}
+ 	
+ 		/*
+ 	 	 * es_result_relation_info must point to the currently active result
+ 	 	 * relation.  We want it to be NULL whenever we're not within
+ 	 	 * ModifyTable, though.
+ 		 */
+ 		estate->es_result_relation_info = resultRelInfo;
  
  		switch (operation)
  		{
  			case CMD_INSERT:
! 				slot = ExecInsert(node->canSetTag, slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(node->canSetTag, tupleid, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(node->canSetTag, tupleid, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
***************
*** 809,833 **** 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;
--- 817,856 ----
  	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
  	mtstate->mt_nplans = nplans;
  	mtstate->operation = operation;
+ 	mtstate->canSetTag = node->canSetTag;
+ 	mtstate->resultRelIndex = node->resultRelIndex;
+ 	mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
+ 
  	/* set up epqstate with dummy subplan pointer for the moment */
  	EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, node->epqParam);
  	mtstate->fireBSTriggers = true;
  
  	/*
  	 * call ExecInitNode on each of the plans to be executed and save the
  	 * results into the array "mt_plans".  Note we *must* set
  	 * estate->es_result_relation_info correctly while we initialize each
  	 * sub-plan; ExecContextForcesOids depends on that!
  	 */
  	i = 0;
+ 	resultRelInfo = mtstate->resultRelInfo;
  	foreach(l, node->plans)
  	{
  		subplan = (Plan *) lfirst(l);
+ 
+ 		/*
+ 		 * If there are indices on the result relation, open them and save
+ 		 * descriptors in the result relation info, so that we can add new index
+ 		 * entries for the tuples we add/update.  We need not do this for a
+ 		 * DELETE, however, since deletion doesn't affect indexes.
+ 		 */
+ 		if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+ 			operation != CMD_DELETE)
+ 			ExecOpenIndices(resultRelInfo);
+ 
+ 		estate->es_result_relation_info = resultRelInfo;
  		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
! 
! 		resultRelInfo++;
  		i++;
  	}
  	estate->es_result_relation_info = NULL;
***************
*** 864,871 **** 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);
--- 887,893 ----
  		/*
  		 * Build a projection for each result rel.
  		 */
! 		resultRelInfo = mtstate->resultRelInfo;
  		foreach(l, node->returningLists)
  		{
  			List	   *rlist = (List *) lfirst(l);
***************
*** 964,970 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  
  		if (junk_filter_needed)
  		{
! 			resultRelInfo = estate->es_result_relations;
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
--- 986,992 ----
  
  		if (junk_filter_needed)
  		{
! 			resultRelInfo = mtstate->resultRelInfo;
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
***************
*** 993,999 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  		else
  		{
  			if (operation == CMD_INSERT)
! 				ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc,
  									subplan->targetlist);
  		}
  	}
--- 1015,1021 ----
  		else
  		{
  			if (operation == CMD_INSERT)
! 				ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
  									subplan->targetlist);
  		}
  	}
*** a/src/backend/executor/nodeSubplan.c
--- b/src/backend/executor/nodeSubplan.c
***************
*** 668,673 **** ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
--- 668,675 ----
  	sstate->planstate = (PlanState *) list_nth(estate->es_subplanstates,
  											   subplan->plan_id - 1);
  
+ 	Assert(sstate->planstate != NULL);
+ 
  	/* Initialize subexpressions */
  	sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
  	sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent);
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 84,89 **** _copyPlannedStmt(PlannedStmt *from)
--- 84,90 ----
  	COPY_NODE_FIELD(resultRelations);
  	COPY_NODE_FIELD(utilityStmt);
  	COPY_NODE_FIELD(intoClause);
+ 	COPY_SCALAR_FIELD(hasDmlWith);
  	COPY_NODE_FIELD(subplans);
  	COPY_BITMAPSET_FIELD(rewindPlanIDs);
  	COPY_NODE_FIELD(rowMarks);
***************
*** 171,177 **** _copyModifyTable(ModifyTable *from)
--- 172,180 ----
  	 * copy remainder of node
  	 */
  	COPY_SCALAR_FIELD(operation);
+ 	COPY_SCALAR_FIELD(canSetTag);
  	COPY_NODE_FIELD(resultRelations);
+ 	COPY_SCALAR_FIELD(resultRelIndex);
  	COPY_NODE_FIELD(plans);
  	COPY_NODE_FIELD(returningLists);
  	COPY_NODE_FIELD(rowMarks);
***************
*** 2260,2265 **** _copyInsertStmt(InsertStmt *from)
--- 2263,2269 ----
  	COPY_NODE_FIELD(cols);
  	COPY_NODE_FIELD(selectStmt);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
***************
*** 2273,2278 **** _copyDeleteStmt(DeleteStmt *from)
--- 2277,2283 ----
  	COPY_NODE_FIELD(usingClause);
  	COPY_NODE_FIELD(whereClause);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
***************
*** 2287,2292 **** _copyUpdateStmt(UpdateStmt *from)
--- 2292,2298 ----
  	COPY_NODE_FIELD(whereClause);
  	COPY_NODE_FIELD(fromClause);
  	COPY_NODE_FIELD(returningList);
+ 	COPY_NODE_FIELD(withClause);
  
  	return newnode;
  }
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 888,893 **** _equalInsertStmt(InsertStmt *a, InsertStmt *b)
--- 888,894 ----
  	COMPARE_NODE_FIELD(cols);
  	COMPARE_NODE_FIELD(selectStmt);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
***************
*** 899,904 **** _equalDeleteStmt(DeleteStmt *a, DeleteStmt *b)
--- 900,906 ----
  	COMPARE_NODE_FIELD(usingClause);
  	COMPARE_NODE_FIELD(whereClause);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
***************
*** 911,916 **** _equalUpdateStmt(UpdateStmt *a, UpdateStmt *b)
--- 913,919 ----
  	COMPARE_NODE_FIELD(whereClause);
  	COMPARE_NODE_FIELD(fromClause);
  	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_NODE_FIELD(withClause);
  
  	return true;
  }
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 2394,2399 **** bool
--- 2394,2449 ----
  					return true;
  			}
  			break;
+ 		case T_InsertStmt:
+ 			{
+ 				InsertStmt *stmt = (InsertStmt *) node;
+ 
+ 				if (walker(stmt->relation, context))
+ 					return true;
+ 				if (walker(stmt->cols, context))
+ 					return true;
+ 				if (walker(stmt->selectStmt, context))
+ 					return true;
+ 				if (walker(stmt->returningList, context))
+ 					return true;
+ 				if (walker(stmt->withClause, context))
+ 					return true;
+ 			}
+ 			break;
+ 		case T_UpdateStmt:
+ 			{
+ 				UpdateStmt *stmt = (UpdateStmt *) node;
+ 
+ 				if (walker(stmt->relation, context))
+ 					return true;
+ 				if (walker(stmt->targetList, context))
+ 					return true;
+ 				if (walker(stmt->whereClause, context))
+ 					return true;
+ 				if (walker(stmt->fromClause, context))
+ 					return true;
+ 				if (walker(stmt->returningList, context))
+ 					return true;
+ 				if (walker(stmt->withClause, context))
+ 					return true;
+ 			}
+ 			break;
+ 		case T_DeleteStmt:
+ 			{
+ 				DeleteStmt *stmt = (DeleteStmt *) node;
+ 
+ 				if (walker(stmt->relation, context))
+ 					return true;
+ 				if (walker(stmt->usingClause, context))
+ 					return true;
+ 				if (walker(stmt->whereClause, context))
+ 					return true;
+ 				if (walker(stmt->returningList, context))
+ 					return true;
+ 				if (walker(stmt->withClause, context))
+ 					return true;
+ 			}
+ 			break;
  		case T_A_Expr:
  			{
  				A_Expr	   *expr = (A_Expr *) node;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 327,332 **** _outModifyTable(StringInfo str, ModifyTable *node)
--- 327,333 ----
  
  	WRITE_ENUM_FIELD(operation, CmdType);
  	WRITE_NODE_FIELD(resultRelations);
+ 	WRITE_INT_FIELD(resultRelIndex);
  	WRITE_NODE_FIELD(plans);
  	WRITE_NODE_FIELD(returningLists);
  	WRITE_NODE_FIELD(rowMarks);
***************
*** 1533,1538 **** _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
--- 1534,1540 ----
  	WRITE_NODE_FIELD(finalrowmarks);
  	WRITE_NODE_FIELD(relationOids);
  	WRITE_NODE_FIELD(invalItems);
+ 	WRITE_NODE_FIELD(resultRelations);
  	WRITE_UINT_FIELD(lastPHId);
  	WRITE_BOOL_FIELD(transientPlan);
  }
***************
*** 1548,1554 **** _outPlannerInfo(StringInfo str, PlannerInfo *node)
  	WRITE_UINT_FIELD(query_level);
  	WRITE_NODE_FIELD(join_rel_list);
  	WRITE_INT_FIELD(join_cur_level);
- 	WRITE_NODE_FIELD(resultRelations);
  	WRITE_NODE_FIELD(init_plans);
  	WRITE_NODE_FIELD(cte_plan_ids);
  	WRITE_NODE_FIELD(eq_classes);
--- 1550,1555 ----
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 3754,3760 **** make_result(PlannerInfo *root,
   * to make it look better sometime.
   */
  ModifyTable *
! make_modifytable(CmdType operation, List *resultRelations,
  				 List *subplans, List *returningLists,
  				 List *rowMarks, int epqParam)
  {
--- 3754,3761 ----
   * to make it look better sometime.
   */
  ModifyTable *
! make_modifytable(CmdType operation, bool canSetTag,
! 				 List *resultRelations,
  				 List *subplans, List *returningLists,
  				 List *rowMarks, int epqParam)
  {
***************
*** 3804,3809 **** make_modifytable(CmdType operation, List *resultRelations,
--- 3805,3811 ----
  		node->plan.targetlist = NIL;
  
  	node->operation = operation;
+ 	node->canSetTag = canSetTag;
  	node->resultRelations = resultRelations;
  	node->plans = subplans;
  	node->returningLists = returningLists;
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 160,165 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
--- 160,167 ----
  	glob->finalrowmarks = NIL;
  	glob->relationOids = NIL;
  	glob->invalItems = NIL;
+ 	glob->hasDmlWith = 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->hasDmlWith = glob->hasDmlWith;
  	result->subplans = glob->subplans;
  	result->rewindPlanIDs = glob->rewindPlanIDs;
  	result->rowMarks = glob->finalrowmarks;
***************
*** 541,547 **** subquery_planner(PlannerGlobal *glob, Query *parse,
  				rowMarks = root->rowMarks;
  
  			plan = (Plan *) make_modifytable(parse->commandType,
! 											 copyObject(root->resultRelations),
  											 list_make1(plan),
  											 returningLists,
  											 rowMarks,
--- 544,551 ----
  				rowMarks = root->rowMarks;
  
  			plan = (Plan *) make_modifytable(parse->commandType,
! 											 parse->canSetTag,
! 											 list_make1_int(parse->resultRelation),
  											 list_make1(plan),
  											 returningLists,
  											 rowMarks,
***************
*** 706,718 **** inheritance_planner(PlannerInfo *root)
  	Query	   *parse = root->parse;
  	int			parentRTindex = parse->resultRelation;
  	List	   *subplans = NIL;
- 	List	   *resultRelations = NIL;
  	List	   *returningLists = NIL;
  	List	   *rtable = NIL;
  	List	   *rowMarks;
  	List	   *tlist;
  	PlannerInfo subroot;
  	ListCell   *l;
  
  	foreach(l, root->append_rel_list)
  	{
--- 710,722 ----
  	Query	   *parse = root->parse;
  	int			parentRTindex = parse->resultRelation;
  	List	   *subplans = NIL;
  	List	   *returningLists = NIL;
  	List	   *rtable = NIL;
  	List	   *rowMarks;
  	List	   *tlist;
  	PlannerInfo subroot;
  	ListCell   *l;
+ 	List	   *resultRelations = NIL;
  
  	foreach(l, root->append_rel_list)
  	{
***************
*** 772,779 **** inheritance_planner(PlannerInfo *root)
  		}
  	}
  
- 	root->resultRelations = resultRelations;
- 
  	/* Mark result as unordered (probably unnecessary) */
  	root->query_pathkeys = NIL;
  
--- 776,781 ----
***************
*** 783,789 **** inheritance_planner(PlannerInfo *root)
  	 */
  	if (subplans == NIL)
  	{
- 		root->resultRelations = list_make1_int(parentRTindex);
  		/* although dummy, it must have a valid tlist for executor */
  		tlist = preprocess_targetlist(root, parse->targetList);
  		return (Plan *) make_result(root,
--- 785,790 ----
***************
*** 818,824 **** inheritance_planner(PlannerInfo *root)
  
  	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
  	return (Plan *) make_modifytable(parse->commandType,
! 									 copyObject(root->resultRelations),
  									 subplans, 
  									 returningLists,
  									 rowMarks,
--- 819,826 ----
  
  	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
  	return (Plan *) make_modifytable(parse->commandType,
! 									 parse->canSetTag,
! 									 resultRelations,
  									 subplans, 
  									 returningLists,
  									 rowMarks,
***************
*** 1668,1679 **** grouping_planner(PlannerInfo *root, double tuple_fraction)
  										  count_est);
  	}
  
- 	/* Compute result-relations list if needed */
- 	if (parse->resultRelation)
- 		root->resultRelations = list_make1_int(parse->resultRelation);
- 	else
- 		root->resultRelations = NIL;
- 
  	/*
  	 * Return the actual output ordering in query_pathkeys for possible use by
  	 * an outer query level.
--- 1670,1675 ----
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 520,525 **** set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
--- 520,529 ----
  											  (Plan *) lfirst(l),
  											  rtoffset);
  				}
+ 
+ 				splan->resultRelIndex = list_length(glob->resultRelations);
+ 				glob->resultRelations = list_concat(glob->resultRelations,
+ 													splan->resultRelations);
  			}
  			break;
  		case T_Append:
*** a/src/backend/optimizer/plan/subselect.c
--- b/src/backend/optimizer/plan/subselect.c
***************
*** 873,888 **** SS_process_ctes(PlannerInfo *root)
  		Bitmapset  *tmpset;
  		int			paramid;
  		Param	   *prm;
  
  		/*
! 		 * Ignore CTEs that are not actually referenced anywhere.
  		 */
! 		if (cte->cterefcount == 0)
  		{
  			/* Make a dummy entry in cte_plan_ids */
  			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
  			continue;
  		}
  
  		/*
  		 * Copy the source Query node.	Probably not necessary, but let's keep
--- 873,906 ----
  		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")));
+ 
+ 			if (root->query_level > 1)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("DML WITH is only allowed at the top level")));
+ 
+ 			root->glob->hasDmlWith = true;
+ 		}
  
  		/*
  		 * Copy the source Query node.	Probably not necessary, but let's keep
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 290,295 **** transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
--- 290,302 ----
  
  	qry->distinctClause = NIL;
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * The USING clause is non-standard SQL syntax, and is equivalent in
  	 * functionality to the FROM list that can be specified for UPDATE. The
***************
*** 342,347 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 349,361 ----
  	qry->commandType = CMD_INSERT;
  	pstate->p_is_insert = true;
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
  	 * VALUES list, or general SELECT input.  We special-case VALUES, both for
***************
*** 366,373 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
  		pstate->p_relnamespace = NIL;
  		sub_varnamespace = pstate->p_varnamespace;
  		pstate->p_varnamespace = NIL;
- 		/* There can't be any outer WITH to worry about */
- 		Assert(pstate->p_ctenamespace == NIL);
  	}
  	else
  	{
--- 380,385 ----
***************
*** 1735,1740 **** transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
--- 1747,1759 ----
  										 true,
  										 ACL_UPDATE);
  
+ 	/* process the WITH clause */
+ 	if (stmt->withClause)
+ 	{
+ 		qry->hasRecursive = stmt->withClause->recursive;
+ 		qry->cteList = transformWithClause(pstate, stmt->withClause);
+ 	}
+ 
  	/*
  	 * the FROM clause is non-standard SQL syntax. We used to be able to do
  	 * this with REPLACE in POSTQUEL so we keep the feature.
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 429,435 **** static TypeName *TableFuncTypeName(List *columns);
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
! %type <with> 	with_clause
  %type <list>	cte_list
  
  %type <list>	window_clause window_definition_list opt_partition_clause
--- 429,435 ----
  %type <boolean> xml_whitespace_option
  
  %type <node> 	common_table_expr
! %type <with> 	with_clause opt_with_clause
  %type <list>	cte_list
  
  %type <list>	window_clause window_definition_list opt_partition_clause
***************
*** 7071,7081 **** DeallocateStmt: DEALLOCATE name
   *****************************************************************************/
  
  InsertStmt:
! 			INSERT INTO qualified_name insert_rest returning_clause
  				{
! 					$4->relation = $3;
! 					$4->returningList = $5;
! 					$$ = (Node *) $4;
  				}
  		;
  
--- 7071,7082 ----
   *****************************************************************************/
  
  InsertStmt:
! 			opt_with_clause INSERT INTO qualified_name insert_rest returning_clause
  				{
! 					$5->relation = $4;
! 					$5->returningList = $6;
! 					$5->withClause = $1;
! 					$$ = (Node *) $5;
  				}
  		;
  
***************
*** 7131,7144 **** returning_clause:
   *
   *****************************************************************************/
  
! DeleteStmt: DELETE_P FROM relation_expr_opt_alias
  			using_clause where_or_current_clause returning_clause
  				{
  					DeleteStmt *n = makeNode(DeleteStmt);
! 					n->relation = $3;
! 					n->usingClause = $4;
! 					n->whereClause = $5;
! 					n->returningList = $6;
  					$$ = (Node *)n;
  				}
  		;
--- 7132,7146 ----
   *
   *****************************************************************************/
  
! DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias
  			using_clause where_or_current_clause returning_clause
  				{
  					DeleteStmt *n = makeNode(DeleteStmt);
! 					n->relation = $4;
! 					n->usingClause = $5;
! 					n->whereClause = $6;
! 					n->returningList = $7;
! 					n->withClause = $1;
  					$$ = (Node *)n;
  				}
  		;
***************
*** 7193,7210 **** opt_nowait:	NOWAIT							{ $$ = TRUE; }
   *
   *****************************************************************************/
  
! UpdateStmt: UPDATE relation_expr_opt_alias
  			SET set_clause_list
  			from_clause
  			where_or_current_clause
  			returning_clause
  				{
  					UpdateStmt *n = makeNode(UpdateStmt);
! 					n->relation = $2;
! 					n->targetList = $4;
! 					n->fromClause = $5;
! 					n->whereClause = $6;
! 					n->returningList = $7;
  					$$ = (Node *)n;
  				}
  		;
--- 7195,7213 ----
   *
   *****************************************************************************/
  
! UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
  			SET set_clause_list
  			from_clause
  			where_or_current_clause
  			returning_clause
  				{
  					UpdateStmt *n = makeNode(UpdateStmt);
! 					n->relation = $3;
! 					n->targetList = $5;
! 					n->fromClause = $6;
! 					n->whereClause = $7;
! 					n->returningList = $8;
! 					n->withClause = $1;
  					$$ = (Node *)n;
  				}
  		;
***************
*** 7530,7546 **** with_clause:
  			}
  		;
  
  cte_list:
  		common_table_expr						{ $$ = list_make1($1); }
  		| 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;
  			}
--- 7533,7553 ----
  			}
  		;
  
+ opt_with_clause:
+ 		with_clause								{ $$ = $1; }
+ 		| /*EMPTY*/								{ $$ = NULL; }
+ 
  cte_list:
  		common_table_expr						{ $$ = list_make1($1); }
  		| 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;
***************
*** 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,
--- 595,613 ----
  		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
***************
*** 314,323 **** markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 				ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
  									   attnum);
  				if (ste == NULL || ste->resjunk)
  					elog(ERROR, "subquery %s does not have attribute %d",
--- 314,333 ----
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
+ 				List		*cteList;
+ 				Query		*ctequery;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 
! 				ctequery = (Query *) cte->ctequery;
! 
! 				if (ctequery->commandType == CMD_SELECT)
! 					cteList = ctequery->targetList;
! 				else
! 					cteList = ctequery->returningList;
! 
! 				ste = get_tle_by_resno(cteList,
  									   attnum);
  				if (ste == NULL || ste->resjunk)
  					elog(ERROR, "subquery %s does not have attribute %d",
***************
*** 1345,1355 **** expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 				ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
! 									   attnum);
  				if (ste == NULL || ste->resjunk)
  					elog(ERROR, "subquery %s does not have attribute %d",
  						 rte->eref->aliasname, attnum);
--- 1355,1374 ----
  			{
  				CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
  				TargetEntry *ste;
+ 				List		*cteList;
+ 				Query		*ctequery;
  
  				/* should be analyzed by now */
  				Assert(IsA(cte->ctequery, Query));
! 
! 				ctequery = (Query *) cte->ctequery;
! 
! 				if (ctequery->commandType == CMD_SELECT)
! 					cteList = ctequery->targetList;
! 				else
! 					cteList = ctequery->returningList;
! 
! 				ste = get_tle_by_resno(cteList, attnum);
  				if (ste == NULL || ste->resjunk)
  					elog(ERROR, "subquery %s does not have attribute %d",
  						 rte->eref->aliasname, attnum);
***************
*** 1372,1378 **** expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
  						 levelsup++)
  						pstate = pstate->parentParseState;
  					mypstate.parentParseState = pstate;
! 					mypstate.p_rtable = ((Query *) cte->ctequery)->rtable;
  					/* don't bother filling the rest of the fake pstate */
  
  					return expandRecordVariable(&mypstate, (Var *) expr, 0);
--- 1391,1397 ----
  						 levelsup++)
  						pstate = pstate->parentParseState;
  					mypstate.parentParseState = pstate;
! 					mypstate.p_rtable = ctequery->rtable;
  					/* don't bother filling the rest of the fake pstate */
  
  					return expandRecordVariable(&mypstate, (Var *) expr, 0);
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 1632,1637 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1632,1641 ----
  	bool		returning = false;
  	Query	   *qual_product = NULL;
  	List	   *rewritten = NIL;
+ 	ListCell	*lc1;
+ 	CommonTableExpr	*cte;
+ 	Query		*ctequery;
+ 	List		*newstuff;
  
  	/*
  	 * If the statement is an update, insert or delete - fire rules on it.
***************
*** 1749,1755 **** RewriteQuery(Query *parsetree, List *rewrite_events)
  				foreach(n, product_queries)
  				{
  					Query	   *pt = (Query *) lfirst(n);
- 					List	   *newstuff;
  
  					newstuff = RewriteQuery(pt, rewrite_events);
  					rewritten = list_concat(rewritten, newstuff);
--- 1753,1758 ----
***************
*** 1804,1809 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1807,1877 ----
  	}
  
  	/*
+ 	 * 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
***************
*** 293,298 **** ChoosePortalStrategy(List *stmts)
--- 293,299 ----
  			if (pstmt->canSetTag)
  			{
  				if (pstmt->commandType == CMD_SELECT &&
+ 					!pstmt->hasDmlWith &&
  					pstmt->utilityStmt == NULL &&
  					pstmt->intoClause == NULL)
  					return PORTAL_ONE_SELECT;
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 3980,3988 **** 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",
--- 3980,3995 ----
  				}
  				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
***************
*** 160,166 **** extern void ExecutorRewind(QueryDesc *queryDesc);
  extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
- 				  CmdType operation,
  				  int instrument_options);
  extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
  extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
--- 160,165 ----
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 376,381 **** typedef struct EState
--- 376,382 ----
  
  	List	   *es_exprcontexts;	/* List of ExprContexts within EState */
  
+ 	List	   *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
***************
*** 850,856 **** 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? */
--- 850,856 ----
  	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? */
***************
*** 880,885 **** typedef struct InsertStmt
--- 880,886 ----
  	List	   *cols;			/* optional: names of the target columns */
  	Node	   *selectStmt;		/* the source SELECT/VALUES, or NULL */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } InsertStmt;
  
  /* ----------------------
***************
*** 893,898 **** typedef struct DeleteStmt
--- 894,900 ----
  	List	   *usingClause;	/* optional using clause for more tables */
  	Node	   *whereClause;	/* qualifications */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } DeleteStmt;
  
  /* ----------------------
***************
*** 907,912 **** typedef struct UpdateStmt
--- 909,915 ----
  	Node	   *whereClause;	/* qualifications */
  	List	   *fromClause;		/* optional from clause for more tables */
  	List	   *returningList;	/* list of expressions to return */
+ 	WithClause *withClause;		/* WITH clause */
  } UpdateStmt;
  
  /* ----------------------
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 55,60 **** typedef struct PlannedStmt
--- 55,62 ----
  
  	IntoClause *intoClause;		/* target for SELECT INTO / CREATE TABLE AS */
  
+ 	bool		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
***************
*** 77,83 **** extern SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
  		   long numGroups, double outputRows);
  extern Result *make_result(PlannerInfo *root, List *tlist,
  			Node *resconstantqual, Plan *subplan);
! extern ModifyTable *make_modifytable(CmdType operation, List *resultRelations,
  									 List *subplans, List *returningLists,
  									 List *rowMarks, int epqParam);
  extern bool is_projection_capable_plan(Plan *plan);
--- 77,84 ----
  		   long numGroups, double outputRows);
  extern Result *make_result(PlannerInfo *root, List *tlist,
  			Node *resconstantqual, Plan *subplan);
! extern ModifyTable *make_modifytable(CmdType operation, bool canSetTag,
! 									 List *resultRelations,
  									 List *subplans, List *returningLists,
  									 List *rowMarks, int epqParam);
  extern bool is_projection_capable_plan(Plan *plan);
*** a/src/test/regress/expected/with.out
--- b/src/test/regress/expected/with.out
***************
*** 1026,1028 **** SELECT * FROM t;
--- 1026,1220 ----
   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;
+ -- 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
+ -- 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
***************
*** 500,502 **** WITH RECURSIVE t(j) AS (
--- 500,623 ----
      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;
+ 
+ -- 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;