Writeable CTE patch - Mailing list pgsql-hackers

From Marko Tiikkaja
Subject Writeable CTE patch
Date
Msg-id 4AFEE835.5070606@cs.helsinki.fi
Whole thread Raw
Responses Re: Writeable CTE patch
List pgsql-hackers
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 >= '2009-10-01' AND
+           foo_date <  '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> |
(<replaceableclass="parameter">insert</replaceable> | <replaceable class="parameter">update</replaceable> |
<replaceableclass="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;

pgsql-hackers by date:

Previous
From: Pavel Stehule
Date:
Subject: Re: Inspection of row types in pl/pgsql and pl/sql
Next
From: Tom Lane
Date:
Subject: Re: Inspection of row types in pl/pgsql and pl/sql