Re: Writeable CTE patch

Поиск
Список
Период
Сортировка
От Marko Tiikkaja
Тема Re: Writeable CTE patch
Дата
Msg-id 4B007233.1030602@cs.helsinki.fi
обсуждение исходный текст
Ответ на Writeable CTE patch  (Marko Tiikkaja <marko.tiikkaja@cs.helsinki.fi>)
Ответы Re: Writeable CTE patch
Список pgsql-hackers
I wrote:
> Attached is the latest version of this patch.

Here's that same patch in context diff format.  Sorry for the noise.


Regards,
Marko Tiikkaja
*** a/doc/src/sgml/queries.sgml
--- b/doc/src/sgml/queries.sgml
***************
*** 1499,1505 **** 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
     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</>
--- 1499,1505 ----
  <synopsis>
  SELECT <replaceable>select_list</replaceable> FROM <replaceable>table_expression</replaceable>
  </synopsis>
!    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,1538 **** 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:

  <programlisting>
  WITH regional_sales AS (
--- 1529,1539 ----
    </indexterm>

    <para>
!    <literal>WITH</> provides a way to write subqueries for use in a
!    larger query.  The subqueries can be thought of as defining
!    temporary tables that exist just for this query.  One use of this
!    feature is to break down complicated queries into simpler parts.
!    An example is:

  <programlisting>
  WITH regional_sales AS (
***************
*** 1560,1565 **** GROUP BY region, product;
--- 1561,1590 ----
    </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
*** a/doc/src/sgml/ref/select.sgml
--- b/doc/src/sgml/ref/select.sgml
***************
*** 58,64 **** SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac

  <phrase>and <replaceable class="parameter">with_query</replaceable> is:</phrase>

!     <replaceable class="parameter">with_query_name</replaceable> [ ( <replaceable
class="parameter">column_name</replaceable>[, ...] ) ] AS ( <replaceable class="parameter">select</replaceable> ) 

  TABLE { [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] | <replaceable
class="parameter">with_query_name</replaceable>} 
  </synopsis>
--- 58,64 ----

  <phrase>and <replaceable class="parameter">with_query</replaceable> is:</phrase>

!     <replaceable class="parameter">with_query_name</replaceable> [ ( <replaceable
class="parameter">column_name</replaceable>[, ...] ) ] AS ( <replaceable class="parameter">select</replaceable> |
(<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>
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2160,2166 **** CopyFrom(CopyState cstate)
              heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);

              if (resultRelInfo->ri_NumIndices > 0)
!                 recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
                                                         estate, false);

              /* AFTER ROW INSERT Triggers */
--- 2160,2167 ----
              heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);

              if (resultRelInfo->ri_NumIndices > 0)
!                 recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
!                                                        slot, &(tuple->t_self),
                                                         estate, false);

              /* AFTER ROW INSERT Triggers */
*** a/src/backend/commands/portalcmds.c
--- b/src/backend/commands/portalcmds.c
***************
*** 48,53 **** PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
--- 48,58 ----
      Portal        portal;
      MemoryContext oldContext;

+     if (stmt->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");

*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 3088,3094 **** 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);
          ResetPerTupleExprContext(ec->estate);
      }
  }
--- 3088,3094 ----
      if (ec->resultRelInfo->ri_NumIndices > 0)
      {
          ExecStoreTuple(&newtup, ec->slot, InvalidBuffer, false);
!         ExecInsertIndexTuples(ec->resultRelInfo, ec->slot, &(newtup.t_self), ec->estate, true);
          ResetPerTupleExprContext(ec->estate);
      }
  }
***************
*** 3214,3220 **** 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);
          ResetPerTupleExprContext(ec->estate);
      }
  }
--- 3214,3220 ----
      if (ec->resultRelInfo->ri_NumIndices > 0)
      {
          ExecStoreTuple(&newtup, ec->slot, InvalidBuffer, false);
!         ExecInsertIndexTuples(ec->resultRelInfo, ec->slot, &(newtup.t_self), ec->estate, true);
          ResetPerTupleExprContext(ec->estate);
      }
  }
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
***************
*** 394,399 **** DefineView(ViewStmt *stmt, const char *queryString)
--- 394,400 ----
      Query       *viewParse;
      Oid            viewOid;
      RangeVar   *view;
+     ListCell   *lc;

      /*
       * Run parse analysis to convert the raw parse tree to a Query.  Note this
***************
*** 412,417 **** DefineView(ViewStmt *stmt, const char *queryString)
--- 413,430 ----
          viewParse->commandType != CMD_SELECT)
          elog(ERROR, "unexpected parse analysis result");

+     /* .. but it doesn't check for DML 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
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 924,939 **** 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);
  }

  /*
--- 924,929 ----
***************
*** 1174,1179 **** ExecutePlan(EState *estate,
--- 1164,1251 ----
       */
      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,1949 **** EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
       * ExecInitSubPlan expects to be able to find these entries.
       * Some of the SubPlans might not be used in the part of the plan tree
       * we intend to run, but since it's not easy to tell which, we just
!      * initialize them all.
       */
      Assert(estate->es_subplanstates == NIL);
      foreach(l, parentestate->es_plannedstmt->subplans)
--- 2015,2022 ----
       * ExecInitSubPlan expects to be able to find these entries.
       * Some of the SubPlans might not be used in the part of the plan tree
       * we intend to run, but since it's not easy to tell which, we just
!      * initialize them all.  However, we will never run ModifyTable nodes in
!      * EvalPlanQual() so don't initialize them.
       */
      Assert(estate->es_subplanstates == NIL);
      foreach(l, parentestate->es_plannedstmt->subplans)
***************
*** 1951,1957 **** EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
          Plan       *subplan = (Plan *) lfirst(l);
          PlanState  *subplanstate;

!         subplanstate = ExecInitNode(subplan, estate, 0);

          estate->es_subplanstates = lappend(estate->es_subplanstates,
                                             subplanstate);
--- 2024,2034 ----
          Plan       *subplan = (Plan *) lfirst(l);
          PlanState  *subplanstate;

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

          estate->es_subplanstates = lappend(estate->es_subplanstates,
                                             subplanstate);
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 968,980 **** ExecCloseIndices(ResultRelInfo *resultRelInfo)
   * ----------------------------------------------------------------
   */
  List *
! ExecInsertIndexTuples(TupleTableSlot *slot,
                        ItemPointer tupleid,
                        EState *estate,
                        bool is_vacuum_full)
  {
      List       *result = NIL;
-     ResultRelInfo *resultRelInfo;
      int            i;
      int            numIndices;
      RelationPtr relationDescs;
--- 968,980 ----
   * ----------------------------------------------------------------
   */
  List *
! ExecInsertIndexTuples(ResultRelInfo* resultRelInfo,
!                       TupleTableSlot *slot,
                        ItemPointer tupleid,
                        EState *estate,
                        bool is_vacuum_full)
  {
      List       *result = NIL;
      int            i;
      int            numIndices;
      RelationPtr relationDescs;
***************
*** 987,993 **** 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;
--- 987,992 ----
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 158,169 **** ExecProcessReturning(ProjectionInfo *projectReturning,
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecInsert(TupleTableSlot *slot,
             TupleTableSlot *planSlot,
             EState *estate)
  {
      HeapTuple    tuple;
-     ResultRelInfo *resultRelInfo;
      Relation    resultRelationDesc;
      Oid            newId;
      List       *recheckIndexes = NIL;
--- 158,169 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecInsert(ResultRelInfo *resultRelInfo,
!            TupleTableSlot *slot,
             TupleTableSlot *planSlot,
             EState *estate)
  {
      HeapTuple    tuple;
      Relation    resultRelationDesc;
      Oid            newId;
      List       *recheckIndexes = NIL;
***************
*** 177,183 **** ExecInsert(TupleTableSlot *slot,
      /*
       * get information on the (current) result relation
       */
-     resultRelInfo = estate->es_result_relation_info;
      resultRelationDesc = resultRelInfo->ri_RelationDesc;

      /*
--- 177,182 ----
***************
*** 247,253 **** ExecInsert(TupleTableSlot *slot,
       * insert index entries for tuple
       */
      if (resultRelInfo->ri_NumIndices > 0)
!         recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
                                                 estate, false);

      /* AFTER ROW INSERT Triggers */
--- 246,253 ----
       * insert index entries for tuple
       */
      if (resultRelInfo->ri_NumIndices > 0)
!         recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
!                                                slot, &(tuple->t_self),
                                                 estate, false);

      /* AFTER ROW INSERT Triggers */
***************
*** 271,282 **** ExecInsert(TupleTableSlot *slot,
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(ItemPointer tupleid,
             TupleTableSlot *planSlot,
             EPQState *epqstate,
             EState *estate)
  {
-     ResultRelInfo *resultRelInfo;
      Relation    resultRelationDesc;
      HTSU_Result result;
      ItemPointerData update_ctid;
--- 271,282 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(ResultRelInfo *resultRelInfo,
!            ItemPointer tupleid,
             TupleTableSlot *planSlot,
             EPQState *epqstate,
             EState *estate)
  {
      Relation    resultRelationDesc;
      HTSU_Result result;
      ItemPointerData update_ctid;
***************
*** 285,291 **** ExecDelete(ItemPointer tupleid,
      /*
       * get information on the (current) result relation
       */
-     resultRelInfo = estate->es_result_relation_info;
      resultRelationDesc = resultRelInfo->ri_RelationDesc;

      /* BEFORE ROW DELETE Triggers */
--- 285,290 ----
***************
*** 414,427 **** ldelete:;
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(ItemPointer tupleid,
             TupleTableSlot *slot,
             TupleTableSlot *planSlot,
             EPQState *epqstate,
             EState *estate)
  {
      HeapTuple    tuple;
-     ResultRelInfo *resultRelInfo;
      Relation    resultRelationDesc;
      HTSU_Result result;
      ItemPointerData update_ctid;
--- 413,426 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(ResultRelInfo *resultRelInfo,
!            ItemPointer tupleid,
             TupleTableSlot *slot,
             TupleTableSlot *planSlot,
             EPQState *epqstate,
             EState *estate)
  {
      HeapTuple    tuple;
      Relation    resultRelationDesc;
      HTSU_Result result;
      ItemPointerData update_ctid;
***************
*** 443,449 **** ExecUpdate(ItemPointer tupleid,
      /*
       * get information on the (current) result relation
       */
-     resultRelInfo = estate->es_result_relation_info;
      resultRelationDesc = resultRelInfo->ri_RelationDesc;

      /* BEFORE ROW UPDATE Triggers */
--- 442,447 ----
***************
*** 561,567 **** 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),
                                                 estate, false);

      /* AFTER ROW UPDATE Triggers */
--- 559,566 ----
       * If it's a HOT update, we mustn't insert new index entries.
       */
      if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
!         recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
!                                                slot, &(tuple->t_self),
                                                 estate, false);

      /* AFTER ROW UPDATE Triggers */
***************
*** 587,601 **** fireBSTriggers(ModifyTableState *node)
      {
          case CMD_INSERT:
              ExecBSInsertTriggers(node->ps.state,
!                                  node->ps.state->es_result_relations);
              break;
          case CMD_UPDATE:
              ExecBSUpdateTriggers(node->ps.state,
!                                  node->ps.state->es_result_relations);
              break;
          case CMD_DELETE:
              ExecBSDeleteTriggers(node->ps.state,
!                                  node->ps.state->es_result_relations);
              break;
          default:
              elog(ERROR, "unknown operation");
--- 586,600 ----
      {
          case CMD_INSERT:
              ExecBSInsertTriggers(node->ps.state,
!                                  node->resultRelInfo);
              break;
          case CMD_UPDATE:
              ExecBSUpdateTriggers(node->ps.state,
!                                  node->resultRelInfo);
              break;
          case CMD_DELETE:
              ExecBSDeleteTriggers(node->ps.state,
!                                  node->resultRelInfo);
              break;
          default:
              elog(ERROR, "unknown operation");
***************
*** 613,627 **** fireASTriggers(ModifyTableState *node)
      {
          case CMD_INSERT:
              ExecASInsertTriggers(node->ps.state,
!                                  node->ps.state->es_result_relations);
              break;
          case CMD_UPDATE:
              ExecASUpdateTriggers(node->ps.state,
!                                  node->ps.state->es_result_relations);
              break;
          case CMD_DELETE:
              ExecASDeleteTriggers(node->ps.state,
!                                  node->ps.state->es_result_relations);
              break;
          default:
              elog(ERROR, "unknown operation");
--- 612,626 ----
      {
          case CMD_INSERT:
              ExecASInsertTriggers(node->ps.state,
!                                  node->resultRelInfo);
              break;
          case CMD_UPDATE:
              ExecASUpdateTriggers(node->ps.state,
!                                  node->resultRelInfo);
              break;
          case CMD_DELETE:
              ExecASDeleteTriggers(node->ps.state,
!                                  node->resultRelInfo);
              break;
          default:
              elog(ERROR, "unknown operation");
***************
*** 643,648 **** ExecModifyTable(ModifyTableState *node)
--- 642,648 ----
      EState *estate = node->ps.state;
      CmdType operation = node->operation;
      PlanState *subplanstate;
+     ResultRelInfo *resultRelInfo;
      JunkFilter *junkfilter;
      TupleTableSlot *slot;
      TupleTableSlot *planSlot;
***************
*** 658,674 **** ExecModifyTable(ModifyTableState *node)
          node->fireBSTriggers = false;
      }

-     /*
-      * es_result_relation_info must point to the currently active result
-      * relation.  (Note we assume that ModifyTable nodes can't be nested.)
-      * We want it to be NULL whenever we're not within ModifyTable, though.
-      */
-     estate->es_result_relation_info =
-         estate->es_result_relations + node->mt_whichplan;
-
      /* Preload local variables */
      subplanstate = node->mt_plans[node->mt_whichplan];
!     junkfilter = estate->es_result_relation_info->ri_junkFilter;

      /*
       * Fetch rows from subplan(s), and execute the required table modification
--- 658,667 ----
          node->fireBSTriggers = false;
      }

      /* Preload local variables */
      subplanstate = node->mt_plans[node->mt_whichplan];
!     resultRelInfo = node->resultRelInfo + node->mt_whichplan;
!     junkfilter = resultRelInfo->ri_junkFilter;

      /*
       * Fetch rows from subplan(s), and execute the required table modification
***************
*** 684,692 **** ExecModifyTable(ModifyTableState *node)
              node->mt_whichplan++;
              if (node->mt_whichplan < node->mt_nplans)
              {
-                 estate->es_result_relation_info++;
                  subplanstate = node->mt_plans[node->mt_whichplan];
!                 junkfilter = estate->es_result_relation_info->ri_junkFilter;
                  EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan);
                  continue;
              }
--- 677,685 ----
              node->mt_whichplan++;
              if (node->mt_whichplan < node->mt_nplans)
              {
                  subplanstate = node->mt_plans[node->mt_whichplan];
!                 resultRelInfo = node->resultRelInfo + node->mt_whichplan;
!                 junkfilter = resultRelInfo->ri_junkFilter;
                  EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan);
                  continue;
              }
***************
*** 728,741 **** ExecModifyTable(ModifyTableState *node)
          switch (operation)
          {
              case CMD_INSERT:
!                 slot = ExecInsert(slot, planSlot, estate);
                  break;
              case CMD_UPDATE:
!                 slot = ExecUpdate(tupleid, slot, planSlot,
                                    &node->mt_epqstate, estate);
                  break;
              case CMD_DELETE:
!                 slot = ExecDelete(tupleid, planSlot,
                                    &node->mt_epqstate, estate);
                  break;
              default:
--- 721,737 ----
          switch (operation)
          {
              case CMD_INSERT:
!                 slot = ExecInsert(resultRelInfo,
!                                   slot, planSlot, estate);
                  break;
              case CMD_UPDATE:
!                 slot = ExecUpdate(resultRelInfo,
!                                   tupleid, slot, planSlot,
                                    &node->mt_epqstate, estate);
                  break;
              case CMD_DELETE:
!                 slot = ExecDelete(resultRelInfo,
!                                   tupleid, planSlot,
                                    &node->mt_epqstate, estate);
                  break;
              default:
***************
*** 748,762 **** 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.
       */
--- 744,752 ----
***************
*** 803,827 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
      mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
      mtstate->mt_nplans = nplans;
      mtstate->operation = operation;
      /* set up epqstate with dummy subplan pointer for the moment */
      EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, node->epqParam);
      mtstate->fireBSTriggers = true;

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

      /*
       * call ExecInitNode on each of the plans to be executed and save the
       * results into the array "mt_plans".  Note we *must* set
       * estate->es_result_relation_info correctly while we initialize each
       * sub-plan; ExecContextForcesOids depends on that!
       */
      i = 0;
+     resultRelInfo = mtstate->resultRelInfo;
      foreach(l, node->plans)
      {
          subplan = (Plan *) lfirst(l);
+
+         /*
+          * If there are indices on the result relation, open them and save
+          * descriptors in the result relation info, so that we can add new index
+          * entries for the tuples we add/update.  We need not do this for a
+          * DELETE, however, since deletion doesn't affect indexes.
+          */
+         if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+             operation != CMD_DELETE)
+             ExecOpenIndices(resultRelInfo);
+
+         estate->es_result_relation_info = resultRelInfo;
          mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
!
!         resultRelInfo++;
          i++;
      }
      estate->es_result_relation_info = NULL;
***************
*** 858,865 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
          /*
           * Build a projection for each result rel.
           */
!         Assert(list_length(node->returningLists) == estate->es_num_result_relations);
!         resultRelInfo = estate->es_result_relations;
          foreach(l, node->returningLists)
          {
              List       *rlist = (List *) lfirst(l);
--- 862,868 ----
          /*
           * Build a projection for each result rel.
           */
!         resultRelInfo = mtstate->resultRelInfo;
          foreach(l, node->returningLists)
          {
              List       *rlist = (List *) lfirst(l);
***************
*** 958,964 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)

          if (junk_filter_needed)
          {
!             resultRelInfo = estate->es_result_relations;
              for (i = 0; i < nplans; i++)
              {
                  JunkFilter *j;
--- 961,967 ----

          if (junk_filter_needed)
          {
!             resultRelInfo = mtstate->resultRelInfo;
              for (i = 0; i < nplans; i++)
              {
                  JunkFilter *j;
***************
*** 987,993 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
          else
          {
              if (operation == CMD_INSERT)
!                 ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc,
                                      subplan->targetlist);
          }
      }
--- 990,996 ----
          else
          {
              if (operation == CMD_INSERT)
!                 ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
                                      subplan->targetlist);
          }
      }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 84,89 **** _copyPlannedStmt(PlannedStmt *from)
--- 84,90 ----
      COPY_NODE_FIELD(resultRelations);
      COPY_NODE_FIELD(utilityStmt);
      COPY_NODE_FIELD(intoClause);
+     COPY_SCALAR_FIELD(hasWritableCtes);
      COPY_NODE_FIELD(subplans);
      COPY_BITMAPSET_FIELD(rewindPlanIDs);
      COPY_NODE_FIELD(rowMarks);
***************
*** 172,177 **** _copyModifyTable(ModifyTable *from)
--- 173,179 ----
       */
      COPY_SCALAR_FIELD(operation);
      COPY_NODE_FIELD(resultRelations);
+     COPY_SCALAR_FIELD(resultRelIndex);
      COPY_NODE_FIELD(plans);
      COPY_NODE_FIELD(returningLists);
      COPY_NODE_FIELD(rowMarks);
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 2383,2388 **** bool
--- 2383,2432 ----
                      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;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 327,332 **** _outModifyTable(StringInfo str, ModifyTable *node)
--- 327,333 ----

      WRITE_ENUM_FIELD(operation, CmdType);
      WRITE_NODE_FIELD(resultRelations);
+     WRITE_INT_FIELD(resultRelIndex);
      WRITE_NODE_FIELD(plans);
      WRITE_NODE_FIELD(returningLists);
      WRITE_NODE_FIELD(rowMarks);
***************
*** 1529,1534 **** _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
--- 1530,1536 ----
      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,1549 **** _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);
--- 1545,1550 ----
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 3759,3768 **** 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.
       */
--- 3759,3764 ----
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 160,165 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
--- 160,167 ----
      glob->finalrowmarks = NIL;
      glob->relationOids = NIL;
      glob->invalItems = NIL;
+     glob->hasWritableCtes = false;
+     glob->resultRelations = NIL;
      glob->lastPHId = 0;
      glob->transientPlan = false;

***************
*** 237,245 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
      result->transientPlan = glob->transientPlan;
      result->planTree = top_plan;
      result->rtable = glob->finalrtable;
!     result->resultRelations = root->resultRelations;
      result->utilityStmt = parse->utilityStmt;
      result->intoClause = parse->intoClause;
      result->subplans = glob->subplans;
      result->rewindPlanIDs = glob->rewindPlanIDs;
      result->rowMarks = glob->finalrowmarks;
--- 239,248 ----
      result->transientPlan = glob->transientPlan;
      result->planTree = top_plan;
      result->rtable = glob->finalrtable;
!     result->resultRelations = glob->resultRelations;
      result->utilityStmt = parse->utilityStmt;
      result->intoClause = parse->intoClause;
+     result->hasWritableCtes = glob->hasWritableCtes;
      result->subplans = glob->subplans;
      result->rewindPlanIDs = glob->rewindPlanIDs;
      result->rowMarks = glob->finalrowmarks;
***************
*** 541,547 **** subquery_planner(PlannerGlobal *glob, Query *parse,
                  rowMarks = root->rowMarks;

              plan = (Plan *) make_modifytable(parse->commandType,
!                                              copyObject(root->resultRelations),
                                               list_make1(plan),
                                               returningLists,
                                               rowMarks,
--- 544,550 ----
                  rowMarks = root->rowMarks;

              plan = (Plan *) make_modifytable(parse->commandType,
!                                              list_make1_int(parse->resultRelation),
                                               list_make1(plan),
                                               returningLists,
                                               rowMarks,
***************
*** 706,718 **** inheritance_planner(PlannerInfo *root)
      Query       *parse = root->parse;
      int            parentRTindex = parse->resultRelation;
      List       *subplans = NIL;
-     List       *resultRelations = NIL;
      List       *returningLists = NIL;
      List       *rtable = NIL;
      List       *rowMarks;
      List       *tlist;
      PlannerInfo subroot;
      ListCell   *l;

      foreach(l, root->append_rel_list)
      {
--- 709,721 ----
      Query       *parse = root->parse;
      int            parentRTindex = parse->resultRelation;
      List       *subplans = NIL;
      List       *returningLists = NIL;
      List       *rtable = NIL;
      List       *rowMarks;
      List       *tlist;
      PlannerInfo subroot;
      ListCell   *l;
+     List       *resultRelations = NIL;

      foreach(l, root->append_rel_list)
      {
***************
*** 772,779 **** inheritance_planner(PlannerInfo *root)
          }
      }

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

--- 775,780 ----
***************
*** 783,789 **** inheritance_planner(PlannerInfo *root)
       */
      if (subplans == NIL)
      {
-         root->resultRelations = list_make1_int(parentRTindex);
          /* although dummy, it must have a valid tlist for executor */
          tlist = preprocess_targetlist(root, parse->targetList);
          return (Plan *) make_result(root,
--- 784,789 ----
***************
*** 818,824 **** inheritance_planner(PlannerInfo *root)

      /* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
      return (Plan *) make_modifytable(parse->commandType,
!                                      copyObject(root->resultRelations),
                                       subplans,
                                       returningLists,
                                       rowMarks,
--- 818,824 ----

      /* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
      return (Plan *) make_modifytable(parse->commandType,
!                                      resultRelations,
                                       subplans,
                                       returningLists,
                                       rowMarks,
***************
*** 1667,1678 **** 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.
--- 1667,1672 ----
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 516,521 **** set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
--- 516,525 ----
                                                (Plan *) lfirst(l),
                                                rtoffset);
                  }
+
+                 splan->resultRelIndex = list_length(glob->resultRelations);
+                 glob->resultRelations = list_concat(glob->resultRelations,
+                                                     splan->resultRelations);
              }
              break;
          case T_Append:
*** a/src/backend/optimizer/plan/subselect.c
--- b/src/backend/optimizer/plan/subselect.c
***************
*** 873,888 **** SS_process_ctes(PlannerInfo *root)
          Bitmapset  *tmpset;
          int            paramid;
          Param       *prm;

          /*
!          * Ignore CTEs that are not actually referenced anywhere.
           */
!         if (cte->cterefcount == 0)
          {
              /* Make a dummy entry in cte_plan_ids */
              root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
              continue;
          }

          /*
           * Copy the source Query node.    Probably not necessary, but let's keep
--- 873,905 ----
          Bitmapset  *tmpset;
          int            paramid;
          Param       *prm;
+         CmdType        cmdType = ((Query *) cte->ctequery)->commandType;

          /*
!          * Ignore SELECT CTEs that are not actually referenced anywhere.
           */
!         if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
          {
              /* Make a dummy entry in cte_plan_ids */
              root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
              continue;
          }
+         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,904 **** SS_process_ctes(PlannerInfo *root)
--- 916,924 ----
                                  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.
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 7374,7379 **** common_table_expr:  name opt_name_list AS select_with_parens
--- 7374,7406 ----
                  n->location = @1;
                  $$ = (Node *) n;
              }
+         | name opt_name_list AS '(' InsertStmt ')'
+             {
+                 CommonTableExpr *n = makeNode(CommonTableExpr);
+                 n->ctename = $1;
+                 n->aliascolnames = $2;
+                 n->ctequery = $5;
+                 n->location = @1;
+                 $$ = (Node *) n;
+             }
+         | name opt_name_list AS '(' UpdateStmt ')'
+             {
+                 CommonTableExpr *n = makeNode(CommonTableExpr);
+                 n->ctename = $1;
+                 n->aliascolnames = $2;
+                 n->ctequery = $5;
+                 n->location = @1;
+                 $$ = (Node *) n;
+             }
+         | name opt_name_list AS '(' DeleteStmt ')'
+             {
+                 CommonTableExpr *n = makeNode(CommonTableExpr);
+                 n->ctename = $1;
+                 n->aliascolnames = $2;
+                 n->ctequery = $5;
+                 n->location = @1;
+                 $$ = (Node *) n;
+             }
          ;

  into_clause:
*** a/src/backend/parser/parse_cte.c
--- b/src/backend/parser/parse_cte.c
***************
*** 18,23 ****
--- 18,24 ----
  #include "nodes/nodeFuncs.h"
  #include "parser/analyze.h"
  #include "parser/parse_cte.h"
+ #include "nodes/plannodes.h"
  #include "utils/builtins.h"


***************
*** 225,246 **** static void
  analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
  {
      Query       *query;
!
!     /* Analysis not done already */
!     Assert(IsA(cte->ctequery, SelectStmt));

      query = parse_sub_analyze(cte->ctequery, pstate, cte, false);
      cte->ctequery = (Node *) query;

      /*
       * Check that we got something reasonable.    Many of these conditions are
       * impossible given restrictions of the grammar, but check 'em anyway.
!      * (These are the same checks as in transformRangeSubselect.)
       */
!     if (!IsA(query, Query) ||
!         query->commandType != CMD_SELECT ||
!         query->utilityStmt != NULL)
!         elog(ERROR, "unexpected non-SELECT command in subquery in WITH");
      if (query->intoClause)
          ereport(ERROR,
                  (errcode(ERRCODE_SYNTAX_ERROR),
--- 226,250 ----
  analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
  {
      Query       *query;
!     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.
!      * 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.
       */
!     Assert(IsA(query, Query) && query->utilityStmt == NULL);
!
      if (query->intoClause)
          ereport(ERROR,
                  (errcode(ERRCODE_SYNTAX_ERROR),
***************
*** 251,257 **** analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
      if (!cte->cterecursive)
      {
          /* Compute the output column names/types if not done yet */
!         analyzeCTETargetList(pstate, cte, query->targetList);
      }
      else
      {
--- 255,261 ----
      if (!cte->cterecursive)
      {
          /* Compute the output column names/types if not done yet */
!         analyzeCTETargetList(pstate, cte, cteList);
      }
      else
      {
***************
*** 269,275 **** analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
          lctyp = list_head(cte->ctecoltypes);
          lctypmod = list_head(cte->ctecoltypmods);
          varattno = 0;
!         foreach(lctlist, query->targetList)
          {
              TargetEntry *te = (TargetEntry *) lfirst(lctlist);
              Node       *texpr;
--- 273,279 ----
          lctyp = list_head(cte->ctecoltypes);
          lctypmod = list_head(cte->ctecoltypmods);
          varattno = 0;
!         foreach(lctlist, cteList)
          {
              TargetEntry *te = (TargetEntry *) lfirst(lctlist);
              Node       *texpr;
*** a/src/backend/parser/parse_relation.c
--- b/src/backend/parser/parse_relation.c
***************
*** 24,29 ****
--- 24,30 ----
  #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"
*** a/src/backend/parser/parse_target.c
--- b/src/backend/parser/parse_target.c
***************
*** 314,323 **** markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
              {
                  CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
                  TargetEntry *ste;

                  /* should be analyzed by now */
                  Assert(IsA(cte->ctequery, Query));
!                 ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
                                         attnum);
                  if (ste == NULL || ste->resjunk)
                      elog(ERROR, "subquery %s does not have attribute %d",
--- 314,333 ----
              {
                  CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
                  TargetEntry *ste;
+                 List        *cteList;
+                 Query        *ctequery;

                  /* should be analyzed by now */
                  Assert(IsA(cte->ctequery, Query));
!
!                 ctequery = (Query *) cte->ctequery;
!
!                 if (ctequery->commandType == CMD_SELECT)
!                     cteList = ctequery->targetList;
!                 else
!                     cteList = ctequery->returningList;
!
!                 ste = get_tle_by_resno(cteList,
                                         attnum);
                  if (ste == NULL || ste->resjunk)
                      elog(ERROR, "subquery %s does not have attribute %d",
***************
*** 1345,1355 **** expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
              {
                  CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
                  TargetEntry *ste;

                  /* should be analyzed by now */
                  Assert(IsA(cte->ctequery, Query));
!                 ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
!                                        attnum);
                  if (ste == NULL || ste->resjunk)
                      elog(ERROR, "subquery %s does not have attribute %d",
                           rte->eref->aliasname, attnum);
--- 1355,1374 ----
              {
                  CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
                  TargetEntry *ste;
+                 List        *cteList;
+                 Query        *ctequery;

                  /* should be analyzed by now */
                  Assert(IsA(cte->ctequery, Query));
!
!                 ctequery = (Query *) cte->ctequery;
!
!                 if (ctequery->commandType == CMD_SELECT)
!                     cteList = ctequery->targetList;
!                 else
!                     cteList = ctequery->returningList;
!
!                 ste = get_tle_by_resno(cteList, attnum);
                  if (ste == NULL || ste->resjunk)
                      elog(ERROR, "subquery %s does not have attribute %d",
                           rte->eref->aliasname, attnum);
***************
*** 1372,1378 **** expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
                           levelsup++)
                          pstate = pstate->parentParseState;
                      mypstate.parentParseState = pstate;
!                     mypstate.p_rtable = ((Query *) cte->ctequery)->rtable;
                      /* don't bother filling the rest of the fake pstate */

                      return expandRecordVariable(&mypstate, (Var *) expr, 0);
--- 1391,1397 ----
                           levelsup++)
                          pstate = pstate->parentParseState;
                      mypstate.parentParseState = pstate;
!                     mypstate.p_rtable = ctequery->rtable;
                      /* don't bother filling the rest of the fake pstate */

                      return expandRecordVariable(&mypstate, (Var *) expr, 0);
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 1632,1637 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1632,1641 ----
      bool        returning = false;
      Query       *qual_product = NULL;
      List       *rewritten = NIL;
+     ListCell    *lc;
+     CommonTableExpr    *cte;
+     Query        *ctequery;
+     List        *newstuff;

      /*
       * If the statement is an update, insert or delete - fire rules on it.
***************
*** 1749,1755 **** RewriteQuery(Query *parsetree, List *rewrite_events)
                  foreach(n, product_queries)
                  {
                      Query       *pt = (Query *) lfirst(n);
-                     List       *newstuff;

                      newstuff = RewriteQuery(pt, rewrite_events);
                      rewritten = list_concat(rewritten, newstuff);
--- 1753,1758 ----
***************
*** 1804,1809 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1807,1861 ----
      }

      /*
+      * 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
*** a/src/backend/tcop/pquery.c
--- b/src/backend/tcop/pquery.c
***************
*** 293,298 **** ChoosePortalStrategy(List *stmts)
--- 293,299 ----
              if (pstmt->canSetTag)
              {
                  if (pstmt->commandType == CMD_SELECT &&
+                     pstmt->hasWritableCtes == false &&
                      pstmt->utilityStmt == NULL &&
                      pstmt->intoClause == NULL)
                      return PORTAL_ONE_SELECT;
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 3858,3866 **** get_name_for_var_field(Var *var, int fieldno,
                  }
                  if (lc != NULL)
                  {
!                     Query       *ctequery = (Query *) cte->ctequery;
!                     TargetEntry *ste = get_tle_by_resno(ctequery->targetList,
!                                                         attnum);

                      if (ste == NULL || ste->resjunk)
                          elog(ERROR, "subquery %s does not have attribute %d",
--- 3858,3873 ----
                  }
                  if (lc != NULL)
                  {
!                     Query        *ctequery = (Query *) cte->ctequery;
!                     List        *ctelist;
!                     TargetEntry    *ste;
!
!                     if (ctequery->commandType != CMD_SELECT)
!                         ctelist = ctequery->returningList;
!                     else
!                         ctelist = ctequery->targetList;
!
!                     ste = get_tle_by_resno(ctelist, attnum);

                      if (ste == NULL || ste->resjunk)
                          elog(ERROR, "subquery %s does not have attribute %d",
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 319,325 **** extern void ExecCloseScanRelation(Relation scanrel);

  extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
  extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
! extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
                        EState *estate, bool is_vacuum_full);

  extern void RegisterExprContextCallback(ExprContext *econtext,
--- 319,326 ----

  extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
  extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
! extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
!                       TupleTableSlot *slot, ItemPointer tupleid,
                        EState *estate, bool is_vacuum_full);

  extern void RegisterExprContextCallback(ExprContext *econtext,
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 1021,1026 **** typedef struct ModifyTableState
--- 1021,1028 ----
      PlanState      **mt_plans;        /* subplans (one per target rel) */
      int                mt_nplans;        /* number of plans in the array */
      int                mt_whichplan;    /* which one is being executed (0..n-1) */
+     int                resultRelIndex;
+     ResultRelInfo  *resultRelInfo;
      EPQState        mt_epqstate;    /* for evaluating EvalPlanQual rechecks */
      bool            fireBSTriggers;    /* do we need to fire stmt triggers? */
  } ModifyTableState;
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 55,60 **** typedef struct PlannedStmt
--- 55,62 ----

      IntoClause *intoClause;        /* target for SELECT INTO / CREATE TABLE AS */

+     bool        hasWritableCtes;
+
      List       *subplans;        /* Plan trees for SubPlan expressions */

      Bitmapset  *rewindPlanIDs;    /* indices of subplans that require REWIND */
***************
*** 165,170 **** typedef struct ModifyTable
--- 167,173 ----
      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) */
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
***************
*** 80,85 **** typedef struct PlannerGlobal
--- 80,89 ----

      List       *invalItems;        /* other dependencies, as PlanInvalItems */

+     bool        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,149 **** 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 */
--- 146,151 ----
*** a/src/test/regress/expected/with.out
--- b/src/test/regress/expected/with.out
***************
*** 1026,1028 **** SELECT * FROM t;
--- 1026,1110 ----
   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)
+
*** a/src/test/regress/sql/with.sql
--- b/src/test/regress/sql/with.sql
***************
*** 500,502 **** WITH RECURSIVE t(j) AS (
--- 500,537 ----
      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 по дате отправления:

Предыдущее
От: Simon Riggs
Дата:
Сообщение: Re: Summary and Plan for Hot Standby
Следующее
От: Greg Smith
Дата:
Сообщение: CommitFest 2009-11 Closed; Initial assignments