Writeable CTEs, again

Поиск
Список
Период
Сортировка
От Marko Tiikkaja
Тема Writeable CTEs, again
Дата
Msg-id 4AE0D7D5.6090804@cs.helsinki.fi
обсуждение исходный текст
Список pgsql-hackers
Hi,

Attached is a WIP patch which implements writeable CTEs.  This patch has
some defects I'll be discussing below.  Also, I haven't implemented the
grammar changes for using WITH ( .. RETURNING ) in non-SELECT queries
yet.

What's not obvious from the patch:
       - estate->es_result_relation_info is currently only set during
         EvalPlanQual().  ModifyTable nodes have an array of
         ResultRelInfos they will be operating on.  That array is part of
         estate->es_result_relations.
       - I removed resultRelations from PlannerInfo completely because I
         didn't find use for it any more.  That list is now stored first
         in ModifyTable nodes, and then added to PlannerGlobal's
         new resultRelations list during set_plan_refs().

Currently, we don't allow DO ALSO SELECT .. rules for SELECT queries.
But with this patch you could have a top-level SELECT which results in
multiple SELECTs when the DML operations inside CTEs are rewritten.
Consider this example:

=> CREATE RULE additional_select AS ON INSERT TO foo DO ALSO SELECT *
FROM bar;

=> WITH t AS (INSERT INTO foo VALUES(0) RETURNING *) SELECT * FROM t;

INSERT INTO foo VALUES(0) is ran first, but the results of that are
ignored.  What you actually see is the output of SELECT * FROM bar which
is certainly surprising.  What do you think should happen here?
INSERT/UPDATE/DELETE works as expected; both queries are ran but you get
the output of SELECT * FROM t;

Currently we also only allow cursors for simple SELECT queries.  IMHO we
should also allow cursor for SELECT queries like the one above; the
INSERT is run to completion first, but then the user could use a cursor
to scan through the RETURNING tuples.  I haven't looked into this very
thoroughly yet, but I don't see any obvious problems.

I'd appreciate any input.

Regards,
Marko Tiikkaja

diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index b2741bc..111ed6a 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,28 @@ GROUP BY region, product;
   </para>

   <para>
+  <literal>WITH</literal> clauses can also have a
+  <literal>INSERT</literal>, <literal>UPDATE</literal>,
+  <literal>DELETE</literal>(each optionally with a
+  <literal>RETURNING</literal> clause) in them.  The example below
+  moves rows from the main table, foo_log into a partition,
+  foo_log_200910.
+
+<programlisting>
+WITH t AS (
+    DELETE FROM foo_log
+    WHERE
+        foo_date >= '2009-10-01' AND
+        foo_date <  '2009-11-01'
+    RETURNING *
+)
+INSERT INTO foo_log_200910
+SELECT * FROM t;
+</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 05d90dc..29cc9db 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/trigger.c b/src/backend/commands/trigger.c
index 756c65c..2446c6f 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2159,7 +2159,7 @@ ltrmark:;
                     TupleTableSlot *epqslot;

                     epqslot = EvalPlanQual(estate,
-                                           relinfo->ri_RangeTableIndex,
+                                           relinfo,
                                            subplanstate,
                                            &update_ctid,
                                            update_xmax);
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c9c9baa..cd63630 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -3067,7 +3067,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);
     }
 }
@@ -3193,7 +3193,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/executor/execMain.c b/src/backend/executor/execMain.c
index 6e79405..7858f97 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -171,7 +171,8 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
         case CMD_SELECT:
             /* SELECT INTO and SELECT FOR UPDATE/SHARE need to mark tuples */
             if (queryDesc->plannedstmt->intoClause != NULL ||
-                queryDesc->plannedstmt->rowMarks != NIL)
+                queryDesc->plannedstmt->rowMarks != NIL ||
+                queryDesc->plannedstmt->hasWritableCtes)
                 estate->es_output_cid = GetCurrentCommandId(true);
             break;

@@ -1173,6 +1174,92 @@ ExecutePlan(EState *estate,
      */
     estate->es_direction = direction;

+    /* Process top-level CTEs in case they have writes inside */
+    {
+        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);
+
+            /*
+             * 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 might 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
+                 *
+                 * XXX this isn't probably the best way to determine whether there's
+                 * an INSERT/UPDATE/DELETE inside the CTE
+                 */
+                if (!IsA(ps, ModifyTableState))
+                    continue;
+
+                /*
+                 * Tell the CTE leader to scan itself and then return
+                 *
+                 * XXX would there be something to gain in using a custom function
+                 * in nodeCtescan.c for this? */
+                for (;;)
+                {
+                    slot = ExecProcNode((PlanState *) leader);
+                    if (TupIsNull(slot))
+                        break;
+                }
+
+                ExecReScan((PlanState *) leader, NULL);
+            }
+
+            /*
+             * bump CID.
+             *
+             * 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;
+
+            /* Should we do something for crosscheck snapshot here? */
+        }
+    }
+
     /*
      * Loop until we've processed the proper number of tuples from the plan.
      */
@@ -1230,7 +1317,6 @@ ExecutePlan(EState *estate,
     }
 }

-
 /*
  * ExecRelCheck --- check that tuple meets constraints for result relation
  */
@@ -1337,7 +1423,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
  * See backend/executor/README for some info about how this works.
  *
  *    estate - executor state data
- *    rti - rangetable index of table containing tuple
+ *    resultRelInfo - ResultRelInfo of table containing tuple
  *    subplanstate - portion of plan tree that needs to be re-evaluated
  *    *tid - t_ctid from the outdated tuple (ie, next updated version)
  *    priorXmax - t_xmax from the outdated tuple
@@ -1349,15 +1435,23 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
  * NULL if we determine we shouldn't process the row.
  */
 TupleTableSlot *
-EvalPlanQual(EState *estate, Index rti,
+EvalPlanQual(EState *estate, ResultRelInfo *resultRelInfo,
              PlanState *subplanstate,
              ItemPointer tid, TransactionId priorXmax)
 {
     TupleTableSlot *slot;
     HeapTuple    copyTuple;
+    Index rti;
+
+    Assert(resultRelInfo != NULL);
+
+    rti = resultRelInfo->ri_RangeTableIndex;

     Assert(rti != 0);

+    /* Set es_result_relation_info correctly for EvalPlanQualFetch() */
+    estate->es_result_relation_info = resultRelInfo;
+
     /*
      * Get the updated version of the row; if fail, return NULL.
      */
@@ -1420,6 +1514,8 @@ EvalPlanQual(EState *estate, Index rti,
      */
     EvalPlanQualPop(estate, subplanstate);

+    estate->es_result_relation_info = NULL;
+
     return slot;
 }

@@ -1892,7 +1988,8 @@ EvalPlanQualStart(evalPlanQual *epq, EState *estate, 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(epqstate->es_subplanstates == NIL);
     foreach(l, estate->es_plannedstmt->subplans)
@@ -1900,7 +1997,11 @@ EvalPlanQualStart(evalPlanQual *epq, EState *estate, Plan *planTree,
         Plan       *subplan = (Plan *) lfirst(l);
         PlanState  *subplanstate;

-        subplanstate = ExecInitNode(subplan, epqstate, 0);
+        /* Don't initialize ModifyTable subplans. */
+        if (IsA(subplan, ModifyTable))
+            subplanstate = NULL;
+        else
+            subplanstate = ExecInitNode(subplan, epqstate, 0);

         epqstate->es_subplanstates = lappend(epqstate->es_subplanstates,
                                              subplanstate);
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 23d987e..dbaa131 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 d623e0b..5f22416 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,
            PlanState *subplanstate,
            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 */
@@ -334,7 +333,7 @@ ldelete:;
                 TupleTableSlot *epqslot;

                 epqslot = EvalPlanQual(estate,
-                                       resultRelInfo->ri_RangeTableIndex,
+                                       resultRelInfo,
                                        subplanstate,
                                        &update_ctid,
                                        update_xmax);
@@ -413,14 +412,14 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(ResultRelInfo *resultRelInfo,
+           ItemPointer tupleid,
            TupleTableSlot *slot,
            TupleTableSlot *planSlot,
            PlanState *subplanstate,
            EState *estate)
 {
     HeapTuple    tuple;
-    ResultRelInfo *resultRelInfo;
     Relation    resultRelationDesc;
     HTSU_Result result;
     ItemPointerData update_ctid;
@@ -442,7 +441,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 */
@@ -520,7 +518,7 @@ lreplace:;
                 TupleTableSlot *epqslot;

                 epqslot = EvalPlanQual(estate,
-                                       resultRelInfo->ri_RangeTableIndex,
+                                       resultRelInfo,
                                        subplanstate,
                                        &update_ctid,
                                        update_xmax);
@@ -559,7 +557,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 */
@@ -585,15 +584,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");
@@ -611,15 +610,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");
@@ -641,6 +640,7 @@ ExecModifyTable(ModifyTableState *node)
     EState *estate = node->ps.state;
     CmdType operation = node->operation;
     PlanState *subplanstate;
+    ResultRelInfo *resultRelInfo;
     JunkFilter *junkfilter;
     TupleTableSlot *slot;
     TupleTableSlot *planSlot;
@@ -656,17 +656,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
@@ -682,9 +675,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;
                 continue;
             }
             else
@@ -724,14 +717,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,
                                   subplanstate, estate);
                 break;
             case CMD_DELETE:
-                slot = ExecDelete(tupleid, planSlot,
+                slot = ExecDelete(resultRelInfo,
+                                  tupleid, planSlot,
                                   subplanstate, estate);
                 break;
             default:
@@ -744,15 +740,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.
      */
@@ -799,23 +789,22 @@ 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;
     mtstate->fireBSTriggers = true;

-    /* For the moment, assume our targets are exactly the global result rels */
-
     /*
      * call ExecInitNode on each of the plans to be executed and save the
      * results into the array "mt_plans".  Note we *must* set
      * estate->es_result_relation_info correctly while we initialize each
      * sub-plan; ExecContextForcesOids depends on that!
      */
-    estate->es_result_relation_info = estate->es_result_relations;
     i = 0;
     foreach(l, node->plans)
     {
         subplan = (Plan *) lfirst(l);
+        estate->es_result_relation_info = mtstate->resultRelInfo + i;
         mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
-        estate->es_result_relation_info++;
         i++;
     }
     estate->es_result_relation_info = NULL;
@@ -851,8 +840,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);
@@ -919,7 +907,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;
@@ -948,7 +936,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 5b9591e..104b341 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);

@@ -1452,7 +1454,7 @@ _copyXmlExpr(XmlExpr *from)

     return newnode;
 }
-
+
 /*
  * _copyNullIfExpr (same as OpExpr)
  */
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 75d5be5..79eef3b 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);
 }
@@ -1511,6 +1512,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_UINT_FIELD(lastRowmarkId);
     WRITE_BOOL_FIELD(transientPlan);
@@ -1526,7 +1528,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 2167eba..4c9a7f5 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -3757,10 +3757,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 5d3d355..328d52c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -158,6 +158,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->lastRowmarkId = 0;
     glob->transientPlan = false;
@@ -236,9 +238,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;
@@ -535,7 +538,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
                 returningLists = NIL;

             plan = (Plan *) make_modifytable(parse->commandType,
-                                             copyObject(root->resultRelations),
+                                             list_make1_int(parse->resultRelation),
                                              list_make1(plan),
                                              returningLists);
         }
@@ -698,12 +701,12 @@ 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       *tlist;
     PlannerInfo subroot;
     ListCell   *l;
+    List       *resultRelations = NIL;

     foreach(l, root->append_rel_list)
     {
@@ -763,8 +766,6 @@ inheritance_planner(PlannerInfo *root)
         }
     }

-    root->resultRelations = resultRelations;
-
     /* Mark result as unordered (probably unnecessary) */
     root->query_pathkeys = NIL;

@@ -774,7 +775,9 @@ inheritance_planner(PlannerInfo *root)
      */
     if (subplans == NIL)
     {
-        root->resultRelations = list_make1_int(parentRTindex);
+        /* XXX what should we do here? */
+        //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,
@@ -799,7 +802,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);
 }
@@ -1637,12 +1640,6 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
                                              parse->rowMarks);
     }

-    /* 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 55922f3..f9bb52f 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -498,16 +498,23 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
                  */
                 Assert(splan->plan.qual == NIL);

-                foreach(l, splan->resultRelations)
-                {
-                    lfirst_int(l) += rtoffset;
-                }
+                Assert(plan->lefttree == NULL && plan->righttree == NULL);
+
                 foreach(l, splan->plans)
                 {
                     lfirst(l) = set_plan_refs(glob,
                                               (Plan *) lfirst(l),
                                               rtoffset);
                 }
+
+                foreach(l, splan->resultRelations)
+                {
+                    lfirst_int(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 8e077f5..cf013b1 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -868,16 +868,26 @@ 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 (cte->cterefcount > 0 &&
+                 cmdType != CMD_SELECT &&
+                 ((Query *) cte->ctequery)->returningList == NIL)
+        {
+            /* XXX Should this be in parse_relation.c? */
+            ereport(ERROR,
+                    (errcode(ERRCODE_SYNTAX_ERROR),
+                     errmsg("INSERT/UPDATE/DELETE without RETURNING is only allowed inside a non-referenced CTE")));
+        }

         /*
          * Copy the source Query node.    Probably not necessary, but let's keep
@@ -894,6 +904,11 @@ SS_process_ctes(PlannerInfo *root)
                                 cte->cterecursive, 0.0,
                                 &subroot);

+        /* XXX Do we really need to know this? */
+        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 b6b32c5..07c65d3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -7371,6 +7371,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 19bfb8e..7ce5dad 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,30 @@ static void
 analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
 {
     Query       *query;
+    List       *cteList;

     /* Analysis not done already */
+    /*
     Assert(IsA(cte->ctequery, SelectStmt));
+    */

     query = parse_sub_analyze(cte->ctequery, pstate, cte);
     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 +260,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 +278,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 df546aa..ac9f137 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"
@@ -1391,8 +1392,8 @@ addRangeTableEntryForCTE(ParseState *pstate,
     rte->ctelevelsup = levelsup;

     /* Self-reference if and only if CTE's parse analysis isn't completed */
-    rte->self_reference = !IsA(cte->ctequery, Query);
-    Assert(cte->cterecursive || !rte->self_reference);
+    rte->self_reference = !IsA(cte->ctequery, Query) && !IsA(cte->ctequery, ModifyTable);
+    Assert(cte->cterecursive || !rte->self_reference || IsA(cte->ctequery, ModifyTable));
     /* Bump the CTE's refcount if this isn't a self-reference */
     if (!rte->self_reference)
         cte->cterefcount++;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index f6e2bbe..e7de319 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -310,10 +310,20 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
             {
                 CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
                 TargetEntry *ste;
+                List        *cteList;
+                Query        *query;

                 /* should be analyzed by now */
                 Assert(IsA(cte->ctequery, Query));
-                ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
+
+                query = (Query *) cte->ctequery;
+
+                if (query->commandType == CMD_SELECT)
+                    cteList = query->targetList;
+                else
+                    cteList = query->returningList;
+
+                ste = get_tle_by_resno(cteList,
                                        attnum);
                 if (ste == NULL || ste->resjunk)
                     elog(ERROR, "subquery %s does not have attribute %d",
@@ -1234,8 +1244,8 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)

                 /* should be analyzed by now */
                 Assert(IsA(cte->ctequery, Query));
-                ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
-                                       attnum);
+
+                ste = get_tle_by_resno(((Query* ) cte->ctequery)->targetList, attnum);
                 if (ste == NULL || ste->resjunk)
                     elog(ERROR, "subquery %s does not have attribute %d",
                          rte->eref->aliasname, attnum);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 38930e1..50d21f3 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1929,6 +1929,51 @@ QueryRewrite(Query *parsetree)
         results = lappend(results, query);
     }

+    {
+        ListCell    *lc;
+        CommonTableExpr    *cte;
+        Query        *ctequery;
+        List        *rewritten;
+
+        foreach(lc, parsetree->cteList)
+        {
+            cte = lfirst(lc);
+
+            ctequery = (Query *) cte->ctequery;
+
+            if (ctequery->commandType == CMD_SELECT)
+                continue;
+
+            rewritten = QueryRewrite(ctequery);
+
+            /*
+             * For UPDATE and DELETE, the actual query is
+             * added to the end of the list.
+             */
+            if (list_length(rewritten) > 1 && ctequery->commandType != CMD_INSERT)
+            {
+                ListCell    *lc;
+                int n = 1;
+
+                foreach(lc, rewritten)
+                {
+                    if (n == list_length(rewritten))
+                        cte->ctequery = (Node *) lfirst(lc);
+                    else
+                        results = lcons((void *) lfirst(lc), results);
+
+                    n++;
+                }
+            }
+            else
+            {
+                cte->ctequery = (Node *) linitial(rewritten);
+                results = list_concat(results,
+                                      list_delete_first(rewritten));
+            }
+        }
+    }
+
     /*
      * Step 3
      *
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 ea88c3b..7a7997f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3853,9 +3853,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 7dfaff0..5666137 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -166,7 +166,7 @@ extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
 extern void ExecConstraints(ResultRelInfo *resultRelInfo,
                 TupleTableSlot *slot, EState *estate);
-extern TupleTableSlot *EvalPlanQual(EState *estate, Index rti,
+extern TupleTableSlot *EvalPlanQual(EState *estate, ResultRelInfo *resultRelInfo,
              PlanState *subplanstate,
              ItemPointer tid, TransactionId priorXmax);
 extern HeapTuple EvalPlanQualFetch(EState *estate, Index rti,
@@ -309,7 +309,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 bd48c32..cc9bfd8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -991,6 +991,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;
     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 9538b8f..7feb72e 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 */
 } ModifyTable;
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index f7eb915..d995d19 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 */

     Index        lastRowmarkId;    /* highest RowMarkClause ID assigned */
@@ -144,8 +148,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 по дате отправления:

Предыдущее
От: Dimitri Fontaine
Дата:
Сообщение: plpgsql EXECUTE will not set FOUND
Следующее
От: "Joshua D. Drake"
Дата:
Сообщение: Re: per table random-page-cost?