Hi,

Attached is the latest version of this patch.

I altered rewriting a bit (I've brought the problems with the previous approach up a couple of times before) and this version should have the expected output in all situations. This patch doesn't allow you to use INSERT/UPDATE/DELETE as the top level statement, but you can get around that by putting the desired top-level statement in a new CTE.

Since the last patch I also moved ExecOpenIndices to nodeModifyTable.c because the top-level executor doesn't know which result relations are opened for which operations.

One thing which has bothered me a while is that there is no clear option for commandType when you have a multiple types of statements in a single Query. In some places it'd help to know that there are multiple different statements. This is now achieved by having hasWritableCtes variable in PlannedStmt, but that doesn't help in places where you don't have access to (or there isn't yet one) PlannedStmt, which has lead me to think that we could have a CMD_MULTI or a similar value to mark these Queries. I haven't taken the time to look at this in detail, but it's something to think about.


Regards,
Marko Tiikkaja
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index b2741bc..3aa7da5 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1499,7 +1499,7 @@ SELECT 3, 'three';
 <synopsis>
 SELECT <replaceable>select_list</replaceable> FROM 
<replaceable>table_expression</replaceable>
 </synopsis>
-   and can appear anywhere a <literal>SELECT</> can.  For example, you can
+   and can appear anywhere a <literal>SELECT</literal> can.  For example, you 
can
    use it as part of a <literal>UNION</>, or attach a
    <replaceable>sort_specification</replaceable> (<literal>ORDER BY</>,
    <literal>LIMIT</>, and/or <literal>OFFSET</>) to it.  <literal>VALUES</>
@@ -1529,10 +1529,11 @@ SELECT <replaceable>select_list</replaceable> FROM 
<replaceable>table_expression
   </indexterm>
 
   <para>
-   <literal>WITH</> provides a way to write subqueries for use in a larger
-   <literal>SELECT</> query.  The subqueries can be thought of as defining
-   temporary tables that exist just for this query.  One use of this feature
-   is to break down complicated queries into simpler parts.  An example is:
+   <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,6 +1561,30 @@ GROUP BY region, product;
   </para>
 
   <para>
+  A <literal>WITH</literal> clause can also have an
+  <literal>INSERT</literal>, <literal>UPDATE</literal> or
+  <literal>DELETE</literal> (each optionally with a
+  <literal>RETURNING</literal> clause) statement in it.  The example below
+  moves rows from the main table, foo_log into a partition,
+  foo_log_200910.
+
+<programlisting>
+WITH rows AS (
+        DELETE FROM ONLY foo_log
+        WHERE
+           foo_date &gt;= '2009-10-01' AND
+           foo_date &lt;  '2009-11-01'
+           RETURNING *
+     ), t AS (
+           INSERT INTO foo_log_200910
+           SELECT * FROM rows
+     )
+VALUES(true);
+</programlisting>
+
+  </para>
+
+  <para>
    The optional <literal>RECURSIVE</> modifier changes <literal>WITH</>
    from a mere syntactic convenience into a feature that accomplishes
    things not otherwise possible in standard SQL.  Using
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 8954693..3634d43 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -58,7 +58,7 @@ 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> )
+    <replaceable class="parameter">with_query_name</replaceable> [ ( 
<replaceable class="parameter">column_name</replaceable> [, ...] ) ] AS ( 
<replaceable class="parameter">select</replaceable> | (<replaceable 
class="parameter">insert</replaceable> | <replaceable 
class="parameter">update</replaceable> | <replaceable 
class="parameter">delete</replaceable> [ RETURNING...]))
 
 TABLE { [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] 
| <replaceable class="parameter">with_query_name</replaceable> }
 </synopsis>
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 9100dd9..78d2344 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2160,7 +2160,8 @@ CopyFrom(CopyState cstate)
                        heap_insert(cstate->rel, tuple, mycid, hi_options, 
bistate);
 
                        if (resultRelInfo->ri_NumIndices > 0)
-                               recheckIndexes = ExecInsertIndexTuples(slot, 
&(tuple->t_self),
+                               recheckIndexes = 
ExecInsertIndexTuples(resultRelInfo,
+                                                                               
                           slot, &(tuple->t_self),
                                                                                
                           estate, false);
 
                        /* AFTER ROW INSERT Triggers */
diff --git a/src/backend/commands/portalcmds.c 
b/src/backend/commands/portalcmds.c
index 9ca2e84..9e1f05d 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -48,6 +48,11 @@ PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
        Portal          portal;
        MemoryContext oldContext;
 
+       if (stmt->hasWritableCtes)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("Non-SELECT cursors are not 
implemented")));
+
        if (cstmt == NULL || !IsA(cstmt, DeclareCursorStmt))
                elog(ERROR, "PerformCursorOpen called for non-cursor query");
 
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7f2e270..a91b49a 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -3088,7 +3088,7 @@ move_chain_tuple(Relation rel,
        if (ec->resultRelInfo->ri_NumIndices > 0)
        {
                ExecStoreTuple(&newtup, ec->slot, InvalidBuffer, false);
-               ExecInsertIndexTuples(ec->slot, &(newtup.t_self), ec->estate, 
true);
+               ExecInsertIndexTuples(ec->resultRelInfo, ec->slot, 
&(newtup.t_self), ec->estate, true);
                ResetPerTupleExprContext(ec->estate);
        }
 }
@@ -3214,7 +3214,7 @@ move_plain_tuple(Relation rel,
        if (ec->resultRelInfo->ri_NumIndices > 0)
        {
                ExecStoreTuple(&newtup, ec->slot, InvalidBuffer, false);
-               ExecInsertIndexTuples(ec->slot, &(newtup.t_self), ec->estate, 
true);
+               ExecInsertIndexTuples(ec->resultRelInfo, ec->slot, 
&(newtup.t_self), ec->estate, true);
                ResetPerTupleExprContext(ec->estate);
        }
 }
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index edb6ae7..0908803 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -394,6 +394,7 @@ DefineView(ViewStmt *stmt, const char *queryString)
        Query      *viewParse;
        Oid                     viewOid;
        RangeVar   *view;
+       ListCell   *lc;
 
        /*
         * Run parse analysis to convert the raw parse tree to a Query.  Note 
this
@@ -412,6 +413,18 @@ DefineView(ViewStmt *stmt, const char *queryString)
                viewParse->commandType != CMD_SELECT)
                elog(ERROR, "unexpected parse analysis result");
 
+       /* .. but it doesn't check for DML inside CTEs */
+       foreach(lc, viewParse->cteList)
+       {
+               CommonTableExpr         *cte;
+
+               cte = (CommonTableExpr *) lfirst(lc);
+               if (((Query *) cte->ctequery)->commandType != CMD_SELECT)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("INSERT/UPDATE/DELETE inside a 
CTE not allowed in a view definition")));
+       }
+
        /*
         * If a list of column names was given, run through and insert these 
into
         * the actual query tree. - thomas 2000-03-08
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 1383123..3247c5c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -924,16 +924,6 @@ 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);
 }
 
 /*
@@ -1174,6 +1164,88 @@ ExecutePlan(EState *estate,
         */
        estate->es_direction = direction;
 
+       /* Process top-level CTEs in case they have writes inside */
+       if (estate->es_plannedstmt->hasWritableCtes)
+       {
+               ListCell *lc;
+
+               foreach(lc, estate->es_plannedstmt->planTree->initPlan)
+               {
+                       SubPlan *sp;
+                       int cte_param_id;
+                       ParamExecData* prmdata;
+                       CteScanState *leader;
+
+                       sp = (SubPlan *) lfirst(lc);
+                       if (sp->subLinkType != CTE_SUBLINK)
+                               continue;
+
+                       cte_param_id = linitial_int(sp->setParam);
+                       prmdata = &(estate->es_param_exec_vals[cte_param_id]);
+                       leader = (CteScanState *) 
DatumGetPointer(prmdata->value);
+
+
+                       /*
+                        * bump CID.
+                        *
+                        * We're currently relying on the fact that there can 
only be
+                        * a SELECT or VALUES as the top-level statement.
+                        *
+                        * XXX we should probably update the snapshot a bit 
differently
+                        */
+                       CommandCounterIncrement();
+                       estate->es_output_cid = GetCurrentCommandId(true);
+                       estate->es_snapshot->curcid = estate->es_output_cid;
+
+                       /*
+                        * If there's no leader, the CTE isn't referenced 
anywhere
+                        * so we can just go ahead and scan the plan
+                        */
+                       if (!leader)
+                       {
+                               TupleTableSlot *slot;
+                               PlanState *ps = (PlanState *) 
list_nth(estate->es_subplanstates,
+                                                                               
                           sp->plan_id - 1);
+
+                               Assert(IsA(ps, ModifyTableState));
+
+                               /*
+                                * We might have a RETURNING here, which means 
that
+                                * we have to loop until the plan returns NULL.
+                                */
+                               for (;;)
+                               {
+                                       slot = ExecProcNode(ps);
+                                       if (TupIsNull(slot))
+                                               break;
+                               }
+                       }
+                       else
+                       {
+                               TupleTableSlot* slot;
+                               PlanState *ps = (PlanState *) 
list_nth(estate->es_subplanstates,
+                                                                               
                           sp->plan_id - 1);
+
+                               /* Regular CTE, ignore */
+                               if (!IsA(ps, ModifyTableState))
+                                       continue;
+
+                               /*
+                                * Scan through the leader CTE so the RETURNING 
tuples are
+                                * stored into the tuple store.
+                                */
+                               for (;;)
+                               {
+                                       slot = ExecProcNode((PlanState *) 
leader);
+                                       if (TupIsNull(slot))
+                                               break;
+                               }
+
+                               ExecReScan((PlanState *) leader, NULL);
+                       }
+               }
+       }
+
        /*
         * Loop until we've processed the proper number of tuples from the plan.
         */
@@ -1943,7 +2015,8 @@ 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.
+        * initialize them all.  However, we will never run ModifyTable nodes in
+        * EvalPlanQual() so don't initialize them.
         */
        Assert(estate->es_subplanstates == NIL);
        foreach(l, parentestate->es_plannedstmt->subplans)
@@ -1951,7 +2024,11 @@ EvalPlanQualStart(EPQState *epqstate, EState 
*parentestate, Plan *planTree)
                Plan       *subplan = (Plan *) lfirst(l);
                PlanState  *subplanstate;
 
-               subplanstate = ExecInitNode(subplan, estate, 0);
+               /* 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);
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index b968473..44f7a47 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -968,13 +968,13 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
  * ----------------------------------------------------------------
  */
 List *
-ExecInsertIndexTuples(TupleTableSlot *slot,
+ExecInsertIndexTuples(ResultRelInfo* resultRelInfo,
+                                         TupleTableSlot *slot,
                                          ItemPointer tupleid,
                                          EState *estate,
                                          bool is_vacuum_full)
 {
        List       *result = NIL;
-       ResultRelInfo *resultRelInfo;
        int                     i;
        int                     numIndices;
        RelationPtr relationDescs;
@@ -987,7 +987,6 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
        /*
         * Get information from the result relation info structure.
         */
-       resultRelInfo = estate->es_result_relation_info;
        numIndices = resultRelInfo->ri_NumIndices;
        relationDescs = resultRelInfo->ri_IndexRelationDescs;
        indexInfoArray = resultRelInfo->ri_IndexRelationInfo;
diff --git a/src/backend/executor/nodeModifyTable.c 
b/src/backend/executor/nodeModifyTable.c
index a4828ac..61f5026 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -158,12 +158,12 @@ ExecProcessReturning(ProjectionInfo *projectReturning,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecInsert(TupleTableSlot *slot,
+ExecInsert(ResultRelInfo *resultRelInfo,
+                  TupleTableSlot *slot,
                   TupleTableSlot *planSlot,
                   EState *estate)
 {
        HeapTuple       tuple;
-       ResultRelInfo *resultRelInfo;
        Relation        resultRelationDesc;
        Oid                     newId;
        List       *recheckIndexes = NIL;
@@ -177,7 +177,6 @@ ExecInsert(TupleTableSlot *slot,
        /*
         * get information on the (current) result relation
         */
-       resultRelInfo = estate->es_result_relation_info;
        resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
        /*
@@ -247,7 +246,8 @@ ExecInsert(TupleTableSlot *slot,
         * insert index entries for tuple
         */
        if (resultRelInfo->ri_NumIndices > 0)
-               recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+               recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
+                                                                               
           slot, &(tuple->t_self),
                                                                                
           estate, false);
 
        /* AFTER ROW INSERT Triggers */
@@ -271,12 +271,12 @@ ExecInsert(TupleTableSlot *slot,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecDelete(ItemPointer tupleid,
+ExecDelete(ResultRelInfo *resultRelInfo,
+                  ItemPointer tupleid,
                   TupleTableSlot *planSlot,
                   EPQState *epqstate,
                   EState *estate)
 {
-       ResultRelInfo *resultRelInfo;
        Relation        resultRelationDesc;
        HTSU_Result result;
        ItemPointerData update_ctid;
@@ -285,7 +285,6 @@ ExecDelete(ItemPointer tupleid,
        /*
         * get information on the (current) result relation
         */
-       resultRelInfo = estate->es_result_relation_info;
        resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
        /* BEFORE ROW DELETE Triggers */
@@ -414,14 +413,14 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(ResultRelInfo *resultRelInfo,
+                  ItemPointer tupleid,
                   TupleTableSlot *slot,
                   TupleTableSlot *planSlot,
                   EPQState *epqstate,
                   EState *estate)
 {
        HeapTuple       tuple;
-       ResultRelInfo *resultRelInfo;
        Relation        resultRelationDesc;
        HTSU_Result result;
        ItemPointerData update_ctid;
@@ -443,7 +442,6 @@ ExecUpdate(ItemPointer tupleid,
        /*
         * get information on the (current) result relation
         */
-       resultRelInfo = estate->es_result_relation_info;
        resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
        /* BEFORE ROW UPDATE Triggers */
@@ -561,7 +559,8 @@ lreplace:;
         * If it's a HOT update, we mustn't insert new index entries.
         */
        if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
-               recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+               recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
+                                                                               
           slot, &(tuple->t_self),
                                                                                
           estate, false);
 
        /* AFTER ROW UPDATE Triggers */
@@ -587,15 +586,15 @@ fireBSTriggers(ModifyTableState *node)
        {
                case CMD_INSERT:
                        ExecBSInsertTriggers(node->ps.state,
-                                                                
node->ps.state->es_result_relations);
+                                                                
node->resultRelInfo);
                        break;
                case CMD_UPDATE:
                        ExecBSUpdateTriggers(node->ps.state,
-                                                                
node->ps.state->es_result_relations);
+                                                                
node->resultRelInfo);
                        break;
                case CMD_DELETE:
                        ExecBSDeleteTriggers(node->ps.state,
-                                                                
node->ps.state->es_result_relations);
+                                                                
node->resultRelInfo);
                        break;
                default:
                        elog(ERROR, "unknown operation");
@@ -613,15 +612,15 @@ fireASTriggers(ModifyTableState *node)
        {
                case CMD_INSERT:
                        ExecASInsertTriggers(node->ps.state,
-                                                                
node->ps.state->es_result_relations);
+                                                                
node->resultRelInfo);
                        break;
                case CMD_UPDATE:
                        ExecASUpdateTriggers(node->ps.state,
-                                                                
node->ps.state->es_result_relations);
+                                                                
node->resultRelInfo);
                        break;
                case CMD_DELETE:
                        ExecASDeleteTriggers(node->ps.state,
-                                                                
node->ps.state->es_result_relations);
+                                                                
node->resultRelInfo);
                        break;
                default:
                        elog(ERROR, "unknown operation");
@@ -643,6 +642,7 @@ ExecModifyTable(ModifyTableState *node)
        EState *estate = node->ps.state;
        CmdType operation = node->operation;
        PlanState *subplanstate;
+       ResultRelInfo *resultRelInfo;
        JunkFilter *junkfilter;
        TupleTableSlot *slot;
        TupleTableSlot *planSlot;
@@ -658,17 +658,10 @@ 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;
+       resultRelInfo = node->resultRelInfo + node->mt_whichplan;
+       junkfilter = resultRelInfo->ri_junkFilter;
 
        /*
         * Fetch rows from subplan(s), and execute the required table 
modification
@@ -684,9 +677,9 @@ 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;
+                               resultRelInfo = node->resultRelInfo + 
node->mt_whichplan;
+                               junkfilter = resultRelInfo->ri_junkFilter;
                                EvalPlanQualSetPlan(&node->mt_epqstate, 
subplanstate->plan);
                                continue;
                        }
@@ -728,14 +721,17 @@ ExecModifyTable(ModifyTableState *node)
                switch (operation)
                {
                        case CMD_INSERT:
-                               slot = ExecInsert(slot, planSlot, estate);
+                               slot = ExecInsert(resultRelInfo,
+                                                                 slot, 
planSlot, estate);
                                break;
                        case CMD_UPDATE:
-                               slot = ExecUpdate(tupleid, slot, planSlot,
+                               slot = ExecUpdate(resultRelInfo,
+                                                                 tupleid, 
slot, planSlot,
                                                                  
&node->mt_epqstate, estate);
                                break;
                        case CMD_DELETE:
-                               slot = ExecDelete(tupleid, planSlot,
+                               slot = ExecDelete(resultRelInfo,
+                                                                 tupleid, 
planSlot,
                                                                  
&node->mt_epqstate, estate);
                                break;
                        default:
@@ -748,15 +744,9 @@ ExecModifyTable(ModifyTableState *node)
                 * the work on next call.
                 */
                if (slot)
-               {
-                       estate->es_result_relation_info = NULL;
                        return slot;
-               }
        }
 
-       /* Reset es_result_relation_info before exiting */
-       estate->es_result_relation_info = NULL;
-
        /*
         * We're done, but fire AFTER STATEMENT triggers before exiting.
         */
@@ -803,25 +793,39 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, 
int eflags)
        mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * 
nplans);
        mtstate->mt_nplans = nplans;
        mtstate->operation = operation;
+       mtstate->resultRelIndex = node->resultRelIndex;
+       mtstate->resultRelInfo = estate->es_result_relations + 
node->resultRelIndex;
+
        /* set up epqstate with dummy subplan pointer for the moment */
        EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, node->epqParam);
        mtstate->fireBSTriggers = true;
 
-       /* 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;
+       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);
-               estate->es_result_relation_info++;
+
+               resultRelInfo++;
                i++;
        }
        estate->es_result_relation_info = NULL;
@@ -858,8 +862,7 @@ 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;
+               resultRelInfo = mtstate->resultRelInfo;
                foreach(l, node->returningLists)
                {
                        List       *rlist = (List *) lfirst(l);
@@ -958,7 +961,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int 
eflags)
 
                if (junk_filter_needed)
                {
-                       resultRelInfo = estate->es_result_relations;
+                       resultRelInfo = mtstate->resultRelInfo;
                        for (i = 0; i < nplans; i++)
                        {
                                JunkFilter *j;
@@ -987,7 +990,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int 
eflags)
                else
                {
                        if (operation == CMD_INSERT)
-                               
ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc,
+                               
ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
                                                                        
subplan->targetlist);
                }
        }
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a30e685..bf63570 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -84,6 +84,7 @@ _copyPlannedStmt(PlannedStmt *from)
        COPY_NODE_FIELD(resultRelations);
        COPY_NODE_FIELD(utilityStmt);
        COPY_NODE_FIELD(intoClause);
+       COPY_SCALAR_FIELD(hasWritableCtes);
        COPY_NODE_FIELD(subplans);
        COPY_BITMAPSET_FIELD(rewindPlanIDs);
        COPY_NODE_FIELD(rowMarks);
@@ -172,6 +173,7 @@ _copyModifyTable(ModifyTable *from)
         */
        COPY_SCALAR_FIELD(operation);
        COPY_NODE_FIELD(resultRelations);
+       COPY_SCALAR_FIELD(resultRelIndex);
        COPY_NODE_FIELD(plans);
        COPY_NODE_FIELD(returningLists);
        COPY_NODE_FIELD(rowMarks);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index b40db0b..5412e01 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2383,6 +2383,50 @@ bool
                                        return true;
                        }
                        break;
+               case T_InsertStmt:
+                       {
+                               InsertStmt *stmt = (InsertStmt *) node;
+
+                               if (walker(stmt->relation, context))
+                                       return true;
+                               if (walker(stmt->cols, context))
+                                       return true;
+                               if (walker(stmt->selectStmt, context))
+                                       return true;
+                               if (walker(stmt->returningList, context))
+                                       return true;
+                       }
+                       break;
+               case T_UpdateStmt:
+                       {
+                               UpdateStmt *stmt = (UpdateStmt *) node;
+
+                               if (walker(stmt->relation, context))
+                                       return true;
+                               if (walker(stmt->targetList, context))
+                                       return true;
+                               if (walker(stmt->whereClause, context))
+                                       return true;
+                               if (walker(stmt->fromClause, context))
+                                       return true;
+                               if (walker(stmt->returningList, context))
+                                       return true;
+                       }
+                       break;
+               case T_DeleteStmt:
+                       {
+                               DeleteStmt *stmt = (DeleteStmt *) node;
+
+                               if (walker(stmt->relation, context))
+                                       return true;
+                               if (walker(stmt->usingClause, context))
+                                       return true;
+                               if (walker(stmt->whereClause, context))
+                                       return true;
+                               if (walker(stmt->returningList, context))
+                                       return true;
+                       }
+                       break;
                case T_A_Expr:
                        {
                                A_Expr     *expr = (A_Expr *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 7a85f92..b981a38 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -327,6 +327,7 @@ _outModifyTable(StringInfo str, ModifyTable *node)
 
        WRITE_ENUM_FIELD(operation, CmdType);
        WRITE_NODE_FIELD(resultRelations);
+       WRITE_INT_FIELD(resultRelIndex);
        WRITE_NODE_FIELD(plans);
        WRITE_NODE_FIELD(returningLists);
        WRITE_NODE_FIELD(rowMarks);
@@ -1529,6 +1530,7 @@ _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
        WRITE_NODE_FIELD(finalrowmarks);
        WRITE_NODE_FIELD(relationOids);
        WRITE_NODE_FIELD(invalItems);
+       WRITE_NODE_FIELD(resultRelations);
        WRITE_UINT_FIELD(lastPHId);
        WRITE_BOOL_FIELD(transientPlan);
 }
@@ -1543,7 +1545,6 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node)
        WRITE_NODE_FIELD(glob);
        WRITE_UINT_FIELD(query_level);
        WRITE_NODE_FIELD(join_rel_list);
-       WRITE_NODE_FIELD(resultRelations);
        WRITE_NODE_FIELD(init_plans);
        WRITE_NODE_FIELD(cte_plan_ids);
        WRITE_NODE_FIELD(eq_classes);
diff --git a/src/backend/optimizer/plan/createplan.c 
b/src/backend/optimizer/plan/createplan.c
index 331963f..4402092 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -3759,10 +3759,6 @@ make_modifytable(CmdType operation, List 
*resultRelations,
        double          total_size;
        ListCell   *subnode;
 
-       Assert(list_length(resultRelations) == list_length(subplans));
-       Assert(returningLists == NIL ||
-                  list_length(resultRelations) == list_length(returningLists));
-
        /*
         * Compute cost as sum of subplan costs.
         */
diff --git a/src/backend/optimizer/plan/planner.c 
b/src/backend/optimizer/plan/planner.c
index 7f2f0c6..b352956 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -160,6 +160,8 @@ standard_planner(Query *parse, int cursorOptions, 
ParamListInfo boundParams)
        glob->finalrowmarks = NIL;
        glob->relationOids = NIL;
        glob->invalItems = NIL;
+       glob->hasWritableCtes = false;
+       glob->resultRelations = NIL;
        glob->lastPHId = 0;
        glob->transientPlan = false;
 
@@ -237,9 +239,10 @@ 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->resultRelations = glob->resultRelations;
        result->utilityStmt = parse->utilityStmt;
        result->intoClause = parse->intoClause;
+       result->hasWritableCtes = glob->hasWritableCtes;
        result->subplans = glob->subplans;
        result->rewindPlanIDs = glob->rewindPlanIDs;
        result->rowMarks = glob->finalrowmarks;
@@ -541,7 +544,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
                                rowMarks = root->rowMarks;
 
                        plan = (Plan *) make_modifytable(parse->commandType,
-                                                                               
         copyObject(root->resultRelations),
+                                                                               
         list_make1_int(parse->resultRelation),
                                                                                
         list_make1(plan),
                                                                                
         returningLists,
                                                                                
         rowMarks,
@@ -706,13 +709,13 @@ 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;
+       List       *resultRelations = NIL;
 
        foreach(l, root->append_rel_list)
        {
@@ -772,8 +775,6 @@ inheritance_planner(PlannerInfo *root)
                }
        }
 
-       root->resultRelations = resultRelations;
-
        /* Mark result as unordered (probably unnecessary) */
        root->query_pathkeys = NIL;
 
@@ -783,7 +784,6 @@ 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,
@@ -818,7 +818,7 @@ 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),
+                                                                        
resultRelations,
                                                                         
subplans, 
                                                                         
returningLists,
                                                                         
rowMarks,
@@ -1667,12 +1667,6 @@ 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.
diff --git a/src/backend/optimizer/plan/setrefs.c 
b/src/backend/optimizer/plan/setrefs.c
index 34c3ea6..47d2c00 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -516,6 +516,10 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int 
rtoffset)
                                                                                
          (Plan *) lfirst(l),
                                                                                
          rtoffset);
                                }
+
+                               splan->resultRelIndex = 
list_length(glob->resultRelations);
+                               glob->resultRelations = 
list_concat(glob->resultRelations,
+                                                                               
                        splan->resultRelations);
                        }
                        break;
                case T_Append:
diff --git a/src/backend/optimizer/plan/subselect.c 
b/src/backend/optimizer/plan/subselect.c
index 291ec99..bd61cc6 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -873,16 +873,33 @@ SS_process_ctes(PlannerInfo *root)
                Bitmapset  *tmpset;
                int                     paramid;
                Param      *prm;
+               CmdType         cmdType = ((Query *) 
cte->ctequery)->commandType;
 
                /*
-                * Ignore CTEs that are not actually referenced anywhere.
+                * Ignore SELECT CTEs that are not actually referenced anywhere.
                 */
-               if (cte->cterefcount == 0)
+               if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
                {
                        /* Make a dummy entry in cte_plan_ids */
                        root->cte_plan_ids = lappend_int(root->cte_plan_ids, 
-1);
                        continue;
                }
+               else if (cmdType != CMD_SELECT)
+               {
+                       /* We don't know reference counts until here */
+                       if (cte->cterefcount > 0 &&
+                               ((Query *) cte->ctequery)->returningList == NIL)
+                       {
+                               ereport(ERROR,
+                                               
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                errmsg("INSERT/UPDATE/DELETE 
without RETURNING is only allowed inside a non-referenced CTE")));
+                       }
+
+                       if (root->query_level > 1)
+                               ereport(ERROR,
+                                               
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                errmsg("INSERT/UPDATE/DELETE 
inside a CTE is only allowed on the top level")));
+               }
 
                /*
                 * Copy the source Query node.  Probably not necessary, but 
let's keep
@@ -899,6 +916,9 @@ SS_process_ctes(PlannerInfo *root)
                                                                
cte->cterecursive, 0.0,
                                                                &subroot);
 
+               if (subroot->parse->commandType != CMD_SELECT)
+                       root->glob->hasWritableCtes = true;
+
                /*
                 * Make a SubPlan node for it.  This is just enough unlike
                 * build_subplan that we can't share code.
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4ed5b06..cc748c7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -7374,6 +7374,33 @@ common_table_expr:  name opt_name_list AS 
select_with_parens
                                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:
diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c
index e2fcc60..f251ec8 100644
--- a/src/backend/parser/parse_cte.c
+++ b/src/backend/parser/parse_cte.c
@@ -18,6 +18,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_cte.h"
+#include "nodes/plannodes.h"
 #include "utils/builtins.h"
 
 
@@ -225,22 +226,25 @@ static void
 analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
 {
        Query      *query;
-
-       /* Analysis not done already */
-       Assert(IsA(cte->ctequery, SelectStmt));
+       List       *cteList;
 
        query = parse_sub_analyze(cte->ctequery, pstate, cte, false);
        cte->ctequery = (Node *) query;
 
+       if (query->commandType == CMD_SELECT)
+               cteList = query->targetList;
+       else
+               cteList = query->returningList;
+
        /*
         * Check that we got something reasonable.      Many of these 
conditions are
         * impossible given restrictions of the grammar, but check 'em anyway.
-        * (These are the same checks as in transformRangeSubselect.)
+        * Note, however, that we can't yet decice whether to allow
+        * INSERT/UPDATE/DELETE without a RETURNING clause or not because we 
don't
+        * know the refcount.
         */
-       if (!IsA(query, Query) ||
-               query->commandType != CMD_SELECT ||
-               query->utilityStmt != NULL)
-               elog(ERROR, "unexpected non-SELECT command in subquery in 
WITH");
+       Assert(IsA(query, Query) && query->utilityStmt == NULL);
+
        if (query->intoClause)
                ereport(ERROR,
                                (errcode(ERRCODE_SYNTAX_ERROR),
@@ -251,7 +255,7 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
        if (!cte->cterecursive)
        {
                /* Compute the output column names/types if not done yet */
-               analyzeCTETargetList(pstate, cte, query->targetList);
+               analyzeCTETargetList(pstate, cte, cteList);
        }
        else
        {
@@ -269,7 +273,7 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
                lctyp = list_head(cte->ctecoltypes);
                lctypmod = list_head(cte->ctecoltypmods);
                varattno = 0;
-               foreach(lctlist, query->targetList)
+               foreach(lctlist, cteList)
                {
                        TargetEntry *te = (TargetEntry *) lfirst(lctlist);
                        Node       *texpr;
diff --git a/src/backend/parser/parse_relation.c 
b/src/backend/parser/parse_relation.c
index 7140758..24a2cc2 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -24,6 +24,7 @@
 #include "funcapi.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/plannodes.h"
 #include "parser/parsetree.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_type.h"
diff --git a/src/backend/parser/parse_target.c 
b/src/backend/parser/parse_target.c
index 6883dc3..e09875d 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -314,10 +314,20 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
                        {
                                CommonTableExpr *cte = GetCTEForRTE(pstate, 
rte, netlevelsup);
                                TargetEntry *ste;
+                               List            *cteList;
+                               Query           *ctequery;
 
                                /* should be analyzed by now */
                                Assert(IsA(cte->ctequery, Query));
-                               ste = get_tle_by_resno(((Query *) 
cte->ctequery)->targetList,
+
+                               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,11 +1355,20 @@ expandRecordVariable(ParseState *pstate, Var *var, int 
levelsup)
                        {
                                CommonTableExpr *cte = GetCTEForRTE(pstate, 
rte, netlevelsup);
                                TargetEntry *ste;
+                               List            *cteList;
+                               Query           *ctequery;
 
                                /* should be analyzed by now */
                                Assert(IsA(cte->ctequery, Query));
-                               ste = get_tle_by_resno(((Query *) 
cte->ctequery)->targetList,
-                                                                          
attnum);
+
+                               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,7 +1391,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int 
levelsup)
                                                 levelsup++)
                                                pstate = 
pstate->parentParseState;
                                        mypstate.parentParseState = pstate;
-                                       mypstate.p_rtable = ((Query *) 
cte->ctequery)->rtable;
+                                       mypstate.p_rtable = ctequery->rtable;
                                        /* don't bother filling the rest of the 
fake pstate */
 
                                        return expandRecordVariable(&mypstate, 
(Var *) expr, 0);
diff --git a/src/backend/rewrite/rewriteHandler.c 
b/src/backend/rewrite/rewriteHandler.c
index 7af481d..2cc05fb 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1632,6 +1632,10 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
        bool            returning = false;
        Query      *qual_product = NULL;
        List       *rewritten = NIL;
+       ListCell        *lc;
+       CommonTableExpr *cte;
+       Query           *ctequery;
+       List            *newstuff;
 
        /*
         * If the statement is an update, insert or delete - fire rules on it.
@@ -1749,7 +1753,6 @@ 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);
@@ -1804,6 +1807,55 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
        }
 
        /*
+        * Rewrite DML statements inside CTEs.  If there are any
+        * DO ALSO rules, they are added to the top level (there
+        * won't be any non-top-level CTEs with DML).
+        */
+       foreach(lc, parsetree->cteList)
+       {
+               cte = lfirst(lc);
+
+               ctequery = (Query *) cte->ctequery;
+
+               if (ctequery->commandType == CMD_SELECT)
+                       continue;
+
+               newstuff = RewriteQuery(ctequery, NIL);
+
+               /*
+                * For UPDATE and DELETE, the actual query is
+                * added to the end of the list.
+                */
+               if (list_length(newstuff) > 1 &&
+                       ctequery->commandType != CMD_INSERT)
+               {
+                       ListCell        *lc;
+                       int n = 1;
+
+                       foreach(lc, newstuff)
+                       {
+                               /*
+                                * If this is the last one, don't add it to the 
results.
+                                * Instead, update the query inside the CTE.
+                                */
+                               if (n == list_length(newstuff))
+                                       cte->ctequery = (Node *) lfirst(lc);
+                               else
+                                       rewritten = lcons((void *) lfirst(lc), 
rewritten);
+                                       
+                               n++;
+                       }
+
+               }
+               else
+               {
+                       cte->ctequery = (Node *) linitial(newstuff);
+                       rewritten = list_concat(rewritten,
+                                                         
list_delete_first(newstuff));
+               }
+       }
+
+       /*
         * For INSERTs, the original query is done first; for UPDATE/DELETE, it 
is
         * done last.  This is needed because update and delete rule actions 
might
         * not do anything if they are invoked after the update or delete is
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 6f46a29..1c6c56e 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -293,6 +293,7 @@ ChoosePortalStrategy(List *stmts)
                        if (pstmt->canSetTag)
                        {
                                if (pstmt->commandType == CMD_SELECT &&
+                                       pstmt->hasWritableCtes == false &&
                                        pstmt->utilityStmt == NULL &&
                                        pstmt->intoClause == NULL)
                                        return PORTAL_ONE_SELECT;
diff --git a/src/backend/utils/adt/ruleutils.c 
b/src/backend/utils/adt/ruleutils.c
index 42b1c6a..be7a1b5 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3858,9 +3858,16 @@ 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);
+                                       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",
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d367b2a..cccc8f6 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -319,7 +319,8 @@ extern void ExecCloseScanRelation(Relation scanrel);
 
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
-extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
+extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
+                                         TupleTableSlot *slot, ItemPointer 
tupleid,
                                          EState *estate, bool is_vacuum_full);
 
 extern void RegisterExprContextCallback(ExprContext *econtext,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9acd5ec..2c84493 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1021,6 +1021,8 @@ typedef struct ModifyTableState
        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;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index d8a89fa..5b67b0a 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -55,6 +55,8 @@ typedef struct PlannedStmt
 
        IntoClause *intoClause;         /* target for SELECT INTO / CREATE 
TABLE AS */
 
+       bool            hasWritableCtes;
+
        List       *subplans;           /* Plan trees for SubPlan expressions */
 
        Bitmapset  *rewindPlanIDs;      /* indices of subplans that require 
REWIND */
@@ -165,6 +167,7 @@ typedef struct ModifyTable
        Plan            plan;
        CmdType         operation;                      /* INSERT, UPDATE, or 
DELETE */
        List       *resultRelations;    /* integer list of RT indexes */
+       int                     resultRelIndex;
        List       *plans;                              /* plan(s) producing 
source data */
        List       *returningLists;             /* per-target-table RETURNING 
tlists */
        List       *rowMarks;                   /* PlanRowMarks (non-locking 
only) */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 4c82106..5f13076 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -80,6 +80,10 @@ typedef struct PlannerGlobal
 
        List       *invalItems;         /* other dependencies, as 
PlanInvalItems */
 
+       bool            hasWritableCtes;/* is there an (INSERT|UPDATE|DELETE) 
.. RETURNING inside a CTE? */
+
+       List       *resultRelations;/* list of result relations */
+
        Index           lastPHId;               /* highest PlaceHolderVar ID 
assigned */
 
        bool            transientPlan;  /* redo plan when TransactionXmin 
changes? */
@@ -142,8 +146,6 @@ typedef struct PlannerInfo
        List       *join_rel_list;      /* list of join-relation RelOptInfos */
        struct HTAB *join_rel_hash; /* optional hashtable for join relations */
 
-       List       *resultRelations;    /* integer list of RT indexes, or NIL */
-
        List       *init_plans;         /* init SubPlans for query */
 
        List       *cte_plan_ids;       /* per-CTE-item list of subplan IDs */
diff --git a/src/test/regress/expected/with.out 
b/src/test/regress/expected/with.out
index a3e94e9..4cfb569 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -1026,3 +1026,85 @@ SELECT * FROM t;
  10
 (55 rows)
 
+--
+-- Writeable CTEs with RETURNING
+--
+WITH t AS (
+    INSERT INTO y
+    VALUES
+        (11),
+        (12),
+        (13),
+        (14),
+        (15),
+        (16),
+        (17),
+        (18),
+        (19),
+        (20)
+    RETURNING *
+)
+SELECT * FROM t;
+ a  
+----
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+(10 rows)
+
+WITH t AS (
+    UPDATE y
+    SET a=a+1
+    RETURNING *
+)
+SELECT * FROM t;
+ a  
+----
+  2
+  3
+  4
+  5
+  6
+  7
+  8
+  9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+(20 rows)
+
+WITH t AS (
+    DELETE FROM y
+    WHERE a <= 10
+    RETURNING *
+)
+SELECT * FROM t;
+ a  
+----
+  2
+  3
+  4
+  5
+  6
+  7
+  8
+  9
+ 10
+(9 rows)
+
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index 2cbaa42..5b35af3 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -500,3 +500,38 @@ WITH RECURSIVE t(j) AS (
     SELECT j+1 FROM t WHERE j < 10
 )
 SELECT * FROM t;
+
+--
+-- Writeable CTEs with RETURNING
+--
+
+WITH t AS (
+    INSERT INTO y
+    VALUES
+        (11),
+        (12),
+        (13),
+        (14),
+        (15),
+        (16),
+        (17),
+        (18),
+        (19),
+        (20)
+    RETURNING *
+)
+SELECT * FROM t;
+
+WITH t AS (
+    UPDATE y
+    SET a=a+1
+    RETURNING *
+)
+SELECT * FROM t;
+
+WITH t AS (
+    DELETE FROM y
+    WHERE a <= 10
+    RETURNING *
+)
+SELECT * FROM t;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to