Re: Using results from INSERT ... RETURNING - Mailing list pgsql-hackers

From Marko Tiikkaja
Subject Re: Using results from INSERT ... RETURNING
Date
Msg-id 4A99B74A.2000801@cs.helsinki.fi
Whole thread Raw
In response to Re: Using results from INSERT ... RETURNING  ("Marko Tiikkaja" <marko.tiikkaja@cs.helsinki.fi>)
Responses Re: Using results from INSERT ... RETURNING  (Marko Tiikkaja <marko.tiikkaja@cs.helsinki.fi>)
List pgsql-hackers
Hi,

This WIP patch refactors the executor by creating nodes for DML (INSERT,
UPDATE, DELETE). It is designed both to clean up the executor and to
help with making it possible to use (INSERT|UPDATE|DELETE) ...RETURNING
inside a WITH clause. At first I thought about removing
PlannedStmt::returningLists, but there are a couple of places where it's
still used, and having it there won't hurt so I didn't touch it.
ExecInitDml() could still be better.

Does anyone see something seriously wrong with it? Ideas and further
improvements are welcome too.

Attached to the upcoming commitfest.

Regards,
Marko Tiikkaja

*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 705,710 **** ExplainNode(Plan *plan, PlanState *planstate,
--- 705,727 ----
          case T_Hash:
              pname = sname = "Hash";
              break;
+         case T_Dml:
+             switch( ((Dml *) plan)->operation)
+             {
+                 case CMD_INSERT:
+                     pname = "INSERT";
+                     break;
+                 case CMD_UPDATE:
+                     pname = "UPDATE";
+                     break;
+                 case CMD_DELETE:
+                     pname = "DELETE";
+                     break;
+                 default:
+                     pname = "???";
+                     break;
+             }
+             break;
          default:
              pname = sname = "???";
              break;
***************
*** 1064,1069 **** ExplainNode(Plan *plan, PlanState *planstate,
--- 1081,1091 ----
                                 ((AppendState *) planstate)->appendplans,
                                 outer_plan, es);
              break;
+         case T_Dml:
+             ExplainMemberNodes(((Dml *) plan)->plans,
+                                ((DmlState *) planstate)->dmlplans,
+                                outer_plan, es);
+             break;
          case T_BitmapAnd:
              ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
                                 ((BitmapAndState *) planstate)->bitmapplans,
*** a/src/backend/executor/Makefile
--- b/src/backend/executor/Makefile
***************
*** 15,21 **** include $(top_builddir)/src/Makefile.global
  OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
         execProcnode.o execQual.o execScan.o execTuples.o \
         execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
!        nodeBitmapAnd.o nodeBitmapOr.o \
         nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \
         nodeHashjoin.o nodeIndexscan.o nodeMaterial.o nodeMergejoin.o \
         nodeNestloop.o nodeFunctionscan.o nodeRecursiveunion.o nodeResult.o \
--- 15,21 ----
  OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
         execProcnode.o execQual.o execScan.o execTuples.o \
         execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
!        nodeBitmapAnd.o nodeBitmapOr.o nodeDml.o \
         nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \
         nodeHashjoin.o nodeIndexscan.o nodeMaterial.o nodeMergejoin.o \
         nodeNestloop.o nodeFunctionscan.o nodeRecursiveunion.o nodeResult.o \
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 77,83 **** typedef struct evalPlanQual

  /* decls for local routines only used within this module */
  static void InitPlan(QueryDesc *queryDesc, int eflags);
- static void ExecCheckPlanOutput(Relation resultRel, List *targetList);
  static void ExecEndPlan(PlanState *planstate, EState *estate);
  static void ExecutePlan(EState *estate, PlanState *planstate,
              CmdType operation,
--- 77,82 ----
***************
*** 86,104 **** static void ExecutePlan(EState *estate, PlanState *planstate,
              DestReceiver *dest);
  static void ExecSelect(TupleTableSlot *slot,
             DestReceiver *dest, EState *estate);
- static void ExecInsert(TupleTableSlot *slot, ItemPointer tupleid,
-            TupleTableSlot *planSlot,
-            DestReceiver *dest, EState *estate);
- static void ExecDelete(ItemPointer tupleid,
-            TupleTableSlot *planSlot,
-            DestReceiver *dest, EState *estate);
- static void ExecUpdate(TupleTableSlot *slot, ItemPointer tupleid,
-            TupleTableSlot *planSlot,
-            DestReceiver *dest, EState *estate);
- static void ExecProcessReturning(ProjectionInfo *projectReturning,
-                      TupleTableSlot *tupleSlot,
-                      TupleTableSlot *planSlot,
-                      DestReceiver *dest);
  static TupleTableSlot *EvalPlanQualNext(EState *estate);
  static void EndEvalPlanQual(EState *estate);
  static void ExecCheckRTPerms(List *rangeTable);
--- 85,90 ----
***************
*** 695,700 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 681,687 ----
                                estate->es_instrument);
              resultRelInfo++;
          }
+
          estate->es_result_relations = resultRelInfos;
          estate->es_num_result_relations = numResultRelations;
          /* Initialize to first or only result rel */
***************
*** 756,765 **** InitPlan(QueryDesc *queryDesc, int eflags)

      /*
       * Initialize the executor "tuple" table.  We need slots for all the plan
!      * nodes, plus possibly output slots for the junkfilter(s). At this point
!      * we aren't sure if we need junkfilters, so just add slots for them
!      * unconditionally.  Also, if it's not a SELECT, set up a slot for use for
!      * trigger output tuples.  Also, one for RETURNING-list evaluation.
       */
      {
          int            nSlots;
--- 743,749 ----

      /*
       * Initialize the executor "tuple" table.  We need slots for all the plan
!      * nodes, plus possibly a slot for use for trigger output tuples.
       */
      {
          int            nSlots;
***************
*** 773,787 **** InitPlan(QueryDesc *queryDesc, int eflags)

              nSlots += ExecCountSlotsNode(subplan);
          }
!         /* Add slots for junkfilter(s) */
!         if (plannedstmt->resultRelations != NIL)
!             nSlots += list_length(plannedstmt->resultRelations);
!         else
!             nSlots += 1;
!         if (operation != CMD_SELECT)
!             nSlots++;            /* for es_trig_tuple_slot */
!         if (plannedstmt->returningLists)
!             nSlots++;            /* for RETURNING projection */

          estate->es_tupleTable = ExecCreateTupleTable(nSlots);

--- 757,769 ----

              nSlots += ExecCountSlotsNode(subplan);
          }
!
!         /*
!          * In SELECT, we might need one for a junkfilter. In DML,
!          * the DML node takes care of reserving slots for
!          * junkfilters, but we need one for es_trig_tuple_slot.
!          */
!         nSlots++;

          estate->es_tupleTable = ExecCreateTupleTable(nSlots);

***************
*** 842,937 **** InitPlan(QueryDesc *queryDesc, int eflags)
      tupType = ExecGetResultType(planstate);

      /*
!      * Initialize the junk filter if needed.  SELECT and INSERT queries need a
!      * filter if there are any junk attrs in the tlist.  UPDATE and DELETE
!      * always need a filter, since there's always a junk 'ctid' attribute
!      * present --- no need to look first.
!      *
!      * This section of code is also a convenient place to verify that the
!      * output of an INSERT or UPDATE matches the target table(s).
       */
      {
          bool        junk_filter_needed = false;
          ListCell   *tlist;

!         switch (operation)
          {
!             case CMD_SELECT:
!             case CMD_INSERT:
!                 foreach(tlist, plan->targetlist)
!                 {
!                     TargetEntry *tle = (TargetEntry *) lfirst(tlist);

!                     if (tle->resjunk)
!                     {
!                         junk_filter_needed = true;
!                         break;
!                     }
!                 }
!                 break;
!             case CMD_UPDATE:
!             case CMD_DELETE:
                  junk_filter_needed = true;
                  break;
!             default:
!                 break;
          }

          if (junk_filter_needed)
          {
-             /*
-              * If there are multiple result relations, each one needs its own
-              * junk filter.  Note this is only possible for UPDATE/DELETE, so
-              * we can't be fooled by some needing a filter and some not.
-              */
              if (list_length(plannedstmt->resultRelations) > 1)
              {
-                 PlanState **appendplans;
-                 int            as_nplans;
-                 ResultRelInfo *resultRelInfo;
-
-                 /* Top plan had better be an Append here. */
-                 Assert(IsA(plan, Append));
-                 Assert(((Append *) plan)->isTarget);
-                 Assert(IsA(planstate, AppendState));
-                 appendplans = ((AppendState *) planstate)->appendplans;
-                 as_nplans = ((AppendState *) planstate)->as_nplans;
-                 Assert(as_nplans == estate->es_num_result_relations);
-                 resultRelInfo = estate->es_result_relations;
-                 for (i = 0; i < as_nplans; i++)
-                 {
-                     PlanState  *subplan = appendplans[i];
-                     JunkFilter *j;
-
-                     if (operation == CMD_UPDATE)
-                         ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
-                                             subplan->plan->targetlist);
-
-                     j = ExecInitJunkFilter(subplan->plan->targetlist,
-                             resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
-                                   ExecAllocTableSlot(estate->es_tupleTable));
-
-                     /*
-                      * Since it must be UPDATE/DELETE, there had better be a
-                      * "ctid" junk attribute in the tlist ... but ctid could
-                      * be at a different resno for each result relation. We
-                      * look up the ctid resnos now and save them in the
-                      * junkfilters.
-                      */
-                     j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
-                     if (!AttributeNumberIsValid(j->jf_junkAttNo))
-                         elog(ERROR, "could not find junk ctid column");
-                     resultRelInfo->ri_junkFilter = j;
-                     resultRelInfo++;
-                 }
-
-                 /*
-                  * Set active junkfilter too; at this point ExecInitAppend has
-                  * already selected an active result relation...
-                  */
-                 estate->es_junkFilter =
-                     estate->es_result_relation_info->ri_junkFilter;
-
                  /*
                   * We currently can't support rowmarks in this case, because
                   * the associated junk CTIDs might have different resnos in
--- 824,852 ----
      tupType = ExecGetResultType(planstate);

      /*
!      * Initialize the junk filter if needed.  SELECT queries need a
!      * filter if there are any junk attrs in the tlist.
       */
+     if (operation == CMD_SELECT)
      {
          bool        junk_filter_needed = false;
          ListCell   *tlist;

!         foreach(tlist, plan->targetlist)
          {
!             TargetEntry *tle = (TargetEntry *) lfirst(tlist);

!             if (tle->resjunk)
!             {
                  junk_filter_needed = true;
                  break;
!             }
          }

          if (junk_filter_needed)
          {
              if (list_length(plannedstmt->resultRelations) > 1)
              {
                  /*
                   * We currently can't support rowmarks in this case, because
                   * the associated junk CTIDs might have different resnos in
***************
*** 944,956 **** InitPlan(QueryDesc *queryDesc, int eflags)
              }
              else
              {
-                 /* Normal case with just one JunkFilter */
                  JunkFilter *j;

-                 if (operation == CMD_INSERT || operation == CMD_UPDATE)
-                     ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc,
-                                         planstate->plan->targetlist);
-
                  j = ExecInitJunkFilter(planstate->plan->targetlist,
                                         tupType->tdhasoid,
                                    ExecAllocTableSlot(estate->es_tupleTable));
--- 859,866 ----
***************
*** 958,975 **** InitPlan(QueryDesc *queryDesc, int eflags)
                  if (estate->es_result_relation_info)
                      estate->es_result_relation_info->ri_junkFilter = j;

!                 if (operation == CMD_SELECT)
!                 {
!                     /* For SELECT, want to return the cleaned tuple type */
!                     tupType = j->jf_cleanTupType;
!                 }
!                 else if (operation == CMD_UPDATE || operation == CMD_DELETE)
!                 {
!                     /* For UPDATE/DELETE, find the ctid junk attr now */
!                     j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
!                     if (!AttributeNumberIsValid(j->jf_junkAttNo))
!                         elog(ERROR, "could not find junk ctid column");
!                 }

                  /* For SELECT FOR UPDATE/SHARE, find the junk attrs now */
                  foreach(l, estate->es_rowMarks)
--- 868,875 ----
                  if (estate->es_result_relation_info)
                      estate->es_result_relation_info->ri_junkFilter = j;

!                 /* For SELECT, want to return the cleaned tuple type */
!                 tupType = j->jf_cleanTupType;

                  /* For SELECT FOR UPDATE/SHARE, find the junk attrs now */
                  foreach(l, estate->es_rowMarks)
***************
*** 999,1055 **** InitPlan(QueryDesc *queryDesc, int eflags)
          }
          else
          {
-             if (operation == CMD_INSERT)
-                 ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc,
-                                     planstate->plan->targetlist);
-
              estate->es_junkFilter = NULL;
              if (estate->es_rowMarks)
                  elog(ERROR, "SELECT FOR UPDATE/SHARE, but no junk columns");
          }
      }

-     /*
-      * Initialize RETURNING projections if needed.
-      */
-     if (plannedstmt->returningLists)
-     {
-         TupleTableSlot *slot;
-         ExprContext *econtext;
-         ResultRelInfo *resultRelInfo;
-
-         /*
-          * We set QueryDesc.tupDesc to be the RETURNING rowtype in this case.
-          * We assume all the sublists will generate the same output tupdesc.
-          */
-         tupType = ExecTypeFromTL((List *) linitial(plannedstmt->returningLists),
-                                  false);
-
-         /* Set up a slot for the output of the RETURNING projection(s) */
-         slot = ExecAllocTableSlot(estate->es_tupleTable);
-         ExecSetSlotDescriptor(slot, tupType);
-         /* Need an econtext too */
-         econtext = CreateExprContext(estate);
-
-         /*
-          * Build a projection for each result rel.    Note that any SubPlans in
-          * the RETURNING lists get attached to the topmost plan node.
-          */
-         Assert(list_length(plannedstmt->returningLists) == estate->es_num_result_relations);
-         resultRelInfo = estate->es_result_relations;
-         foreach(l, plannedstmt->returningLists)
-         {
-             List       *rlist = (List *) lfirst(l);
-             List       *rliststate;
-
-             rliststate = (List *) ExecInitExpr((Expr *) rlist, planstate);
-             resultRelInfo->ri_projectReturning =
-                 ExecBuildProjectionInfo(rliststate, econtext, slot,
-                                      resultRelInfo->ri_RelationDesc->rd_att);
-             resultRelInfo++;
-         }
-     }
-
      queryDesc->tupDesc = tupType;
      queryDesc->planstate = planstate;

--- 899,910 ----
***************
*** 1151,1225 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
  }

  /*
-  * Verify that the tuples to be produced by INSERT or UPDATE match the
-  * target relation's rowtype
-  *
-  * We do this to guard against stale plans.  If plan invalidation is
-  * functioning properly then we should never get a failure here, but better
-  * safe than sorry.  Note that this is called after we have obtained lock
-  * on the target rel, so the rowtype can't change underneath us.
-  *
-  * The plan output is represented by its targetlist, because that makes
-  * handling the dropped-column case easier.
-  */
- static void
- ExecCheckPlanOutput(Relation resultRel, List *targetList)
- {
-     TupleDesc    resultDesc = RelationGetDescr(resultRel);
-     int            attno = 0;
-     ListCell   *lc;
-
-     foreach(lc, targetList)
-     {
-         TargetEntry *tle = (TargetEntry *) lfirst(lc);
-         Form_pg_attribute attr;
-
-         if (tle->resjunk)
-             continue;            /* ignore junk tlist items */
-
-         if (attno >= resultDesc->natts)
-             ereport(ERROR,
-                     (errcode(ERRCODE_DATATYPE_MISMATCH),
-                      errmsg("table row type and query-specified row type do not match"),
-                      errdetail("Query has too many columns.")));
-         attr = resultDesc->attrs[attno++];
-
-         if (!attr->attisdropped)
-         {
-             /* Normal case: demand type match */
-             if (exprType((Node *) tle->expr) != attr->atttypid)
-                 ereport(ERROR,
-                         (errcode(ERRCODE_DATATYPE_MISMATCH),
-                          errmsg("table row type and query-specified row type do not match"),
-                          errdetail("Table has type %s at ordinal position %d, but query expects %s.",
-                                    format_type_be(attr->atttypid),
-                                    attno,
-                              format_type_be(exprType((Node *) tle->expr)))));
-         }
-         else
-         {
-             /*
-              * For a dropped column, we can't check atttypid (it's likely 0).
-              * In any case the planner has most likely inserted an INT4 null.
-              * What we insist on is just *some* NULL constant.
-              */
-             if (!IsA(tle->expr, Const) ||
-                 !((Const *) tle->expr)->constisnull)
-                 ereport(ERROR,
-                         (errcode(ERRCODE_DATATYPE_MISMATCH),
-                          errmsg("table row type and query-specified row type do not match"),
-                          errdetail("Query provides a value for a dropped column at ordinal position %d.",
-                                    attno)));
-         }
-     }
-     if (attno != resultDesc->natts)
-         ereport(ERROR,
-                 (errcode(ERRCODE_DATATYPE_MISMATCH),
-           errmsg("table row type and query-specified row type do not match"),
-                  errdetail("Query has too few columns.")));
- }
-
- /*
   *        ExecGetTriggerResultRel
   *
   * Get a ResultRelInfo for a trigger target relation.  Most of the time,
--- 1006,1011 ----
***************
*** 1449,1456 **** ExecutePlan(EState *estate,
      JunkFilter *junkfilter;
      TupleTableSlot *planSlot;
      TupleTableSlot *slot;
-     ItemPointer tupleid = NULL;
-     ItemPointerData tuple_ctid;
      long        current_tuple_count;

      /*
--- 1235,1240 ----
***************
*** 1521,1527 **** lnext:    ;
           *
           * But first, extract all the junk information we need.
           */
!         if ((junkfilter = estate->es_junkFilter) != NULL)
          {
              /*
               * Process any FOR UPDATE or FOR SHARE locking requested.
--- 1305,1311 ----
           *
           * But first, extract all the junk information we need.
           */
!         if (operation == CMD_SELECT && (junkfilter = estate->es_junkFilter) != NULL)
          {
              /*
               * Process any FOR UPDATE or FOR SHARE locking requested.
***************
*** 1630,1661 **** lnext:    ;
                  }
              }

!             /*
!              * extract the 'ctid' junk attribute.
!              */
!             if (operation == CMD_UPDATE || operation == CMD_DELETE)
!             {
!                 Datum        datum;
!                 bool        isNull;
!
!                 datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
!                                              &isNull);
!                 /* shouldn't ever get a null result... */
!                 if (isNull)
!                     elog(ERROR, "ctid is NULL");
!
!                 tupleid = (ItemPointer) DatumGetPointer(datum);
!                 tuple_ctid = *tupleid;    /* make sure we don't free the ctid!! */
!                 tupleid = &tuple_ctid;
!             }
!
!             /*
!              * Create a new "clean" tuple with all junk attributes removed. We
!              * don't need to do this for DELETE, however (there will in fact
!              * be no non-junk attributes in a DELETE!)
!              */
!             if (operation != CMD_DELETE)
!                 slot = ExecFilterJunk(junkfilter, slot);
          }

          /*
--- 1414,1420 ----
                  }
              }

!             slot = ExecFilterJunk(junkfilter, slot);
          }

          /*
***************
*** 1670,1684 **** lnext:    ;
                  break;

              case CMD_INSERT:
-                 ExecInsert(slot, tupleid, planSlot, dest, estate);
-                 break;
-
              case CMD_DELETE:
-                 ExecDelete(tupleid, planSlot, dest, estate);
-                 break;
-
              case CMD_UPDATE:
!                 ExecUpdate(slot, tupleid, planSlot, dest, estate);
                  break;

              default:
--- 1429,1438 ----
                  break;

              case CMD_INSERT:
              case CMD_DELETE:
              case CMD_UPDATE:
!                 if (estate->es_plannedstmt->returningLists)
!                     (*dest->receiveSlot) (slot, dest);
                  break;

              default:
***************
*** 1734,2153 **** ExecSelect(TupleTableSlot *slot,
      (estate->es_processed)++;
  }

- /* ----------------------------------------------------------------
-  *        ExecInsert
-  *
-  *        INSERTs are trickier.. we have to insert the tuple into
-  *        the base relation and insert appropriate tuples into the
-  *        index relations.
-  * ----------------------------------------------------------------
-  */
- static void
- ExecInsert(TupleTableSlot *slot,
-            ItemPointer tupleid,
-            TupleTableSlot *planSlot,
-            DestReceiver *dest,
-            EState *estate)
- {
-     HeapTuple    tuple;
-     ResultRelInfo *resultRelInfo;
-     Relation    resultRelationDesc;
-     Oid            newId;
-     List       *recheckIndexes = NIL;
-
-     /*
-      * get the heap tuple out of the tuple table slot, making sure we have a
-      * writable copy
-      */
-     tuple = ExecMaterializeSlot(slot);
-
-     /*
-      * get information on the (current) result relation
-      */
-     resultRelInfo = estate->es_result_relation_info;
-     resultRelationDesc = resultRelInfo->ri_RelationDesc;
-
-     /*
-      * If the result relation has OIDs, force the tuple's OID to zero so that
-      * heap_insert will assign a fresh OID.  Usually the OID already will be
-      * zero at this point, but there are corner cases where the plan tree can
-      * return a tuple extracted literally from some table with the same
-      * rowtype.
-      *
-      * XXX if we ever wanted to allow users to assign their own OIDs to new
-      * rows, this'd be the place to do it.  For the moment, we make a point of
-      * doing this before calling triggers, so that a user-supplied trigger
-      * could hack the OID if desired.
-      */
-     if (resultRelationDesc->rd_rel->relhasoids)
-         HeapTupleSetOid(tuple, InvalidOid);
-
-     /* BEFORE ROW INSERT Triggers */
-     if (resultRelInfo->ri_TrigDesc &&
-         resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
-     {
-         HeapTuple    newtuple;
-
-         newtuple = ExecBRInsertTriggers(estate, resultRelInfo, tuple);
-
-         if (newtuple == NULL)    /* "do nothing" */
-             return;
-
-         if (newtuple != tuple)    /* modified by Trigger(s) */
-         {
-             /*
-              * Put the modified tuple into a slot for convenience of routines
-              * below.  We assume the tuple was allocated in per-tuple memory
-              * context, and therefore will go away by itself. The tuple table
-              * slot should not try to clear it.
-              */
-             TupleTableSlot *newslot = estate->es_trig_tuple_slot;
-
-             if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor)
-                 ExecSetSlotDescriptor(newslot, slot->tts_tupleDescriptor);
-             ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
-             slot = newslot;
-             tuple = newtuple;
-         }
-     }
-
-     /*
-      * Check the constraints of the tuple
-      */
-     if (resultRelationDesc->rd_att->constr)
-         ExecConstraints(resultRelInfo, slot, estate);
-
-     /*
-      * insert the tuple
-      *
-      * Note: heap_insert returns the tid (location) of the new tuple in the
-      * t_self field.
-      */
-     newId = heap_insert(resultRelationDesc, tuple,
-                         estate->es_output_cid, 0, NULL);
-
-     IncrAppended();
-     (estate->es_processed)++;
-     estate->es_lastoid = newId;
-     setLastTid(&(tuple->t_self));
-
-     /*
-      * insert index entries for tuple
-      */
-     if (resultRelInfo->ri_NumIndices > 0)
-         recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-                                                estate, false);
-
-     /* AFTER ROW INSERT Triggers */
-     ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
-
-     /* Process RETURNING if present */
-     if (resultRelInfo->ri_projectReturning)
-         ExecProcessReturning(resultRelInfo->ri_projectReturning,
-                              slot, planSlot, dest);
- }
-
- /* ----------------------------------------------------------------
-  *        ExecDelete
-  *
-  *        DELETE is like UPDATE, except that we delete the tuple and no
-  *        index modifications are needed
-  * ----------------------------------------------------------------
-  */
- static void
- ExecDelete(ItemPointer tupleid,
-            TupleTableSlot *planSlot,
-            DestReceiver *dest,
-            EState *estate)
- {
-     ResultRelInfo *resultRelInfo;
-     Relation    resultRelationDesc;
-     HTSU_Result result;
-     ItemPointerData update_ctid;
-     TransactionId update_xmax;
-
-     /*
-      * get information on the (current) result relation
-      */
-     resultRelInfo = estate->es_result_relation_info;
-     resultRelationDesc = resultRelInfo->ri_RelationDesc;
-
-     /* BEFORE ROW DELETE Triggers */
-     if (resultRelInfo->ri_TrigDesc &&
-         resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0)
-     {
-         bool        dodelete;
-
-         dodelete = ExecBRDeleteTriggers(estate, resultRelInfo, tupleid);
-
-         if (!dodelete)            /* "do nothing" */
-             return;
-     }
-
-     /*
-      * delete the tuple
-      *
-      * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
-      * the row to be deleted is visible to that snapshot, and throw a can't-
-      * serialize error if not.    This is a special-case behavior needed for
-      * referential integrity updates in serializable transactions.
-      */
- ldelete:;
-     result = heap_delete(resultRelationDesc, tupleid,
-                          &update_ctid, &update_xmax,
-                          estate->es_output_cid,
-                          estate->es_crosscheck_snapshot,
-                          true /* wait for commit */ );
-     switch (result)
-     {
-         case HeapTupleSelfUpdated:
-             /* already deleted by self; nothing to do */
-             return;
-
-         case HeapTupleMayBeUpdated:
-             break;
-
-         case HeapTupleUpdated:
-             if (IsXactIsoLevelSerializable)
-                 ereport(ERROR,
-                         (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-                          errmsg("could not serialize access due to concurrent update")));
-             else if (!ItemPointerEquals(tupleid, &update_ctid))
-             {
-                 TupleTableSlot *epqslot;
-
-                 epqslot = EvalPlanQual(estate,
-                                        resultRelInfo->ri_RangeTableIndex,
-                                        &update_ctid,
-                                        update_xmax);
-                 if (!TupIsNull(epqslot))
-                 {
-                     *tupleid = update_ctid;
-                     goto ldelete;
-                 }
-             }
-             /* tuple already deleted; nothing to do */
-             return;
-
-         default:
-             elog(ERROR, "unrecognized heap_delete status: %u", result);
-             return;
-     }
-
-     IncrDeleted();
-     (estate->es_processed)++;
-
-     /*
-      * Note: Normally one would think that we have to delete index tuples
-      * associated with the heap tuple now...
-      *
-      * ... but in POSTGRES, we have no need to do this because VACUUM will
-      * take care of it later.  We can't delete index tuples immediately
-      * anyway, since the tuple is still visible to other transactions.
-      */
-
-     /* AFTER ROW DELETE Triggers */
-     ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
-
-     /* Process RETURNING if present */
-     if (resultRelInfo->ri_projectReturning)
-     {
-         /*
-          * We have to put the target tuple into a slot, which means first we
-          * gotta fetch it.    We can use the trigger tuple slot.
-          */
-         TupleTableSlot *slot = estate->es_trig_tuple_slot;
-         HeapTupleData deltuple;
-         Buffer        delbuffer;
-
-         deltuple.t_self = *tupleid;
-         if (!heap_fetch(resultRelationDesc, SnapshotAny,
-                         &deltuple, &delbuffer, false, NULL))
-             elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
-
-         if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
-             ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
-         ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
-
-         ExecProcessReturning(resultRelInfo->ri_projectReturning,
-                              slot, planSlot, dest);
-
-         ExecClearTuple(slot);
-         ReleaseBuffer(delbuffer);
-     }
- }
-
- /* ----------------------------------------------------------------
-  *        ExecUpdate
-  *
-  *        note: we can't run UPDATE queries with transactions
-  *        off because UPDATEs are actually INSERTs and our
-  *        scan will mistakenly loop forever, updating the tuple
-  *        it just inserted..    This should be fixed but until it
-  *        is, we don't want to get stuck in an infinite loop
-  *        which corrupts your database..
-  * ----------------------------------------------------------------
-  */
- static void
- ExecUpdate(TupleTableSlot *slot,
-            ItemPointer tupleid,
-            TupleTableSlot *planSlot,
-            DestReceiver *dest,
-            EState *estate)
- {
-     HeapTuple    tuple;
-     ResultRelInfo *resultRelInfo;
-     Relation    resultRelationDesc;
-     HTSU_Result result;
-     ItemPointerData update_ctid;
-     TransactionId update_xmax;
-     List *recheckIndexes = NIL;
-
-     /*
-      * abort the operation if not running transactions
-      */
-     if (IsBootstrapProcessingMode())
-         elog(ERROR, "cannot UPDATE during bootstrap");
-
-     /*
-      * get the heap tuple out of the tuple table slot, making sure we have a
-      * writable copy
-      */
-     tuple = ExecMaterializeSlot(slot);
-
-     /*
-      * get information on the (current) result relation
-      */
-     resultRelInfo = estate->es_result_relation_info;
-     resultRelationDesc = resultRelInfo->ri_RelationDesc;
-
-     /* BEFORE ROW UPDATE Triggers */
-     if (resultRelInfo->ri_TrigDesc &&
-         resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0)
-     {
-         HeapTuple    newtuple;
-
-         newtuple = ExecBRUpdateTriggers(estate, resultRelInfo,
-                                         tupleid, tuple);
-
-         if (newtuple == NULL)    /* "do nothing" */
-             return;
-
-         if (newtuple != tuple)    /* modified by Trigger(s) */
-         {
-             /*
-              * Put the modified tuple into a slot for convenience of routines
-              * below.  We assume the tuple was allocated in per-tuple memory
-              * context, and therefore will go away by itself. The tuple table
-              * slot should not try to clear it.
-              */
-             TupleTableSlot *newslot = estate->es_trig_tuple_slot;
-
-             if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor)
-                 ExecSetSlotDescriptor(newslot, slot->tts_tupleDescriptor);
-             ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
-             slot = newslot;
-             tuple = newtuple;
-         }
-     }
-
-     /*
-      * Check the constraints of the tuple
-      *
-      * If we generate a new candidate tuple after EvalPlanQual testing, we
-      * must loop back here and recheck constraints.  (We don't need to redo
-      * triggers, however.  If there are any BEFORE triggers then trigger.c
-      * will have done heap_lock_tuple to lock the correct tuple, so there's no
-      * need to do them again.)
-      */
- lreplace:;
-     if (resultRelationDesc->rd_att->constr)
-         ExecConstraints(resultRelInfo, slot, estate);
-
-     /*
-      * replace the heap tuple
-      *
-      * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
-      * the row to be updated is visible to that snapshot, and throw a can't-
-      * serialize error if not.    This is a special-case behavior needed for
-      * referential integrity updates in serializable transactions.
-      */
-     result = heap_update(resultRelationDesc, tupleid, tuple,
-                          &update_ctid, &update_xmax,
-                          estate->es_output_cid,
-                          estate->es_crosscheck_snapshot,
-                          true /* wait for commit */ );
-     switch (result)
-     {
-         case HeapTupleSelfUpdated:
-             /* already deleted by self; nothing to do */
-             return;
-
-         case HeapTupleMayBeUpdated:
-             break;
-
-         case HeapTupleUpdated:
-             if (IsXactIsoLevelSerializable)
-                 ereport(ERROR,
-                         (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-                          errmsg("could not serialize access due to concurrent update")));
-             else if (!ItemPointerEquals(tupleid, &update_ctid))
-             {
-                 TupleTableSlot *epqslot;
-
-                 epqslot = EvalPlanQual(estate,
-                                        resultRelInfo->ri_RangeTableIndex,
-                                        &update_ctid,
-                                        update_xmax);
-                 if (!TupIsNull(epqslot))
-                 {
-                     *tupleid = update_ctid;
-                     slot = ExecFilterJunk(estate->es_junkFilter, epqslot);
-                     tuple = ExecMaterializeSlot(slot);
-                     goto lreplace;
-                 }
-             }
-             /* tuple already deleted; nothing to do */
-             return;
-
-         default:
-             elog(ERROR, "unrecognized heap_update status: %u", result);
-             return;
-     }
-
-     IncrReplaced();
-     (estate->es_processed)++;
-
-     /*
-      * Note: instead of having to update the old index tuples associated with
-      * the heap tuple, all we do is form and insert new index tuples. This is
-      * because UPDATEs are actually DELETEs and INSERTs, and index tuple
-      * deletion is done later by VACUUM (see notes in ExecDelete).    All we do
-      * here is insert new index tuples.  -cim 9/27/89
-      */
-
-     /*
-      * insert index entries for tuple
-      *
-      * Note: heap_update returns the tid (location) of the new tuple in the
-      * t_self field.
-      *
-      * 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 */
-     ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
-                          recheckIndexes);
-
-     /* Process RETURNING if present */
-     if (resultRelInfo->ri_projectReturning)
-         ExecProcessReturning(resultRelInfo->ri_projectReturning,
-                              slot, planSlot, dest);
- }
-
  /*
   * ExecRelCheck --- check that tuple meets constraints for result relation
   */
--- 1488,1493 ----
***************
*** 2248,2289 **** ExecConstraints(ResultRelInfo *resultRelInfo,
  }

  /*
-  * ExecProcessReturning --- evaluate a RETURNING list and send to dest
-  *
-  * projectReturning: RETURNING projection info for current result rel
-  * tupleSlot: slot holding tuple actually inserted/updated/deleted
-  * planSlot: slot holding tuple returned by top plan node
-  * dest: where to send the output
-  */
- static void
- ExecProcessReturning(ProjectionInfo *projectReturning,
-                      TupleTableSlot *tupleSlot,
-                      TupleTableSlot *planSlot,
-                      DestReceiver *dest)
- {
-     ExprContext *econtext = projectReturning->pi_exprContext;
-     TupleTableSlot *retSlot;
-
-     /*
-      * Reset per-tuple memory context to free any expression evaluation
-      * storage allocated in the previous cycle.
-      */
-     ResetExprContext(econtext);
-
-     /* Make tuple and any needed join variables available to ExecProject */
-     econtext->ecxt_scantuple = tupleSlot;
-     econtext->ecxt_outertuple = planSlot;
-
-     /* Compute the RETURNING expressions */
-     retSlot = ExecProject(projectReturning, NULL);
-
-     /* Send to dest */
-     (*dest->receiveSlot) (retSlot, dest);
-
-     ExecClearTuple(retSlot);
- }
-
- /*
   * Check a modified tuple to see if we want to process its updated version
   * under READ COMMITTED rules.
   *
--- 1588,1593 ----
*** a/src/backend/executor/execProcnode.c
--- b/src/backend/executor/execProcnode.c
***************
*** 91,96 ****
--- 91,97 ----
  #include "executor/nodeHash.h"
  #include "executor/nodeHashjoin.h"
  #include "executor/nodeIndexscan.h"
+ #include "executor/nodeDml.h"
  #include "executor/nodeLimit.h"
  #include "executor/nodeMaterial.h"
  #include "executor/nodeMergejoin.h"
***************
*** 286,291 **** ExecInitNode(Plan *node, EState *estate, int eflags)
--- 287,297 ----
                                                   estate, eflags);
              break;

+         case T_Dml:
+             result = (PlanState *) ExecInitDml((Dml *) node,
+                                                  estate, eflags);
+             break;
+
          default:
              elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
              result = NULL;        /* keep compiler quiet */
***************
*** 451,456 **** ExecProcNode(PlanState *node)
--- 457,466 ----
              result = ExecLimit((LimitState *) node);
              break;

+         case T_DmlState:
+             result = ExecDml((DmlState *) node);
+             break;
+
          default:
              elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
              result = NULL;
***************
*** 627,632 **** ExecCountSlotsNode(Plan *node)
--- 637,645 ----
          case T_Limit:
              return ExecCountSlotsLimit((Limit *) node);

+         case T_Dml:
+             return ExecCountSlotsDml((Dml *) node);
+
          default:
              elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
              break;
***************
*** 783,788 **** ExecEndNode(PlanState *node)
--- 796,805 ----
              ExecEndLimit((LimitState *) node);
              break;

+         case T_DmlState:
+             ExecEndDml((DmlState *) node);
+             break;
+
          default:
              elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
              break;
*** a/src/backend/executor/nodeAppend.c
--- b/src/backend/executor/nodeAppend.c
***************
*** 103,123 **** exec_append_initialize_next(AppendState *appendstate)
      }
      else
      {
-         /*
-          * initialize the scan
-          *
-          * If we are controlling the target relation, select the proper active
-          * ResultRelInfo and junk filter for this target.
-          */
-         if (((Append *) appendstate->ps.plan)->isTarget)
-         {
-             Assert(whichplan < estate->es_num_result_relations);
-             estate->es_result_relation_info =
-                 estate->es_result_relations + whichplan;
-             estate->es_junkFilter =
-                 estate->es_result_relation_info->ri_junkFilter;
-         }
-
          return TRUE;
      }
  }
--- 103,108 ----
***************
*** 164,189 **** ExecInitAppend(Append *node, EState *estate, int eflags)
      appendstate->appendplans = appendplanstates;
      appendstate->as_nplans = nplans;

!     /*
!      * Do we want to scan just one subplan?  (Special case for EvalPlanQual)
!      * XXX pretty dirty way of determining that this case applies ...
!      */
!     if (node->isTarget && estate->es_evTuple != NULL)
!     {
!         int            tplan;
!
!         tplan = estate->es_result_relation_info - estate->es_result_relations;
!         Assert(tplan >= 0 && tplan < nplans);
!
!         appendstate->as_firstplan = tplan;
!         appendstate->as_lastplan = tplan;
!     }
!     else
!     {
!         /* normal case, scan all subplans */
!         appendstate->as_firstplan = 0;
!         appendstate->as_lastplan = nplans - 1;
!     }

      /*
       * Miscellaneous initialization
--- 149,157 ----
      appendstate->appendplans = appendplanstates;
      appendstate->as_nplans = nplans;

!
!     appendstate->as_firstplan = 0;
!     appendstate->as_lastplan = nplans - 1;

      /*
       * Miscellaneous initialization
*** /dev/null
--- b/src/backend/executor/nodeDml.c
***************
*** 0 ****
--- 1,829 ----
+ #include "postgres.h"
+
+ #include "access/xact.h"
+ #include "parser/parsetree.h"
+ #include "executor/executor.h"
+ #include "executor/execdebug.h"
+ #include "executor/nodeDml.h"
+ #include "commands/trigger.h"
+ #include "nodes/nodeFuncs.h"
+ #include "utils/memutils.h"
+ #include "utils/builtins.h"
+ #include "utils/tqual.h"
+ #include "storage/bufmgr.h"
+ #include "miscadmin.h"
+
+ /*
+  * Verify that the tuples to be produced by INSERT or UPDATE match the
+  * target relation's rowtype
+  *
+  * We do this to guard against stale plans.  If plan invalidation is
+  * functioning properly then we should never get a failure here, but better
+  * safe than sorry.  Note that this is called after we have obtained lock
+  * on the target rel, so the rowtype can't change underneath us.
+  *
+  * The plan output is represented by its targetlist, because that makes
+  * handling the dropped-column case easier.
+  */
+ static void
+ ExecCheckPlanOutput(Relation resultRel, List *targetList)
+ {
+     TupleDesc    resultDesc = RelationGetDescr(resultRel);
+     int            attno = 0;
+     ListCell   *lc;
+
+     foreach(lc, targetList)
+     {
+         TargetEntry *tle = (TargetEntry *) lfirst(lc);
+         Form_pg_attribute attr;
+
+         if (tle->resjunk)
+             continue;            /* ignore junk tlist items */
+
+         if (attno >= resultDesc->natts)
+             ereport(ERROR,
+                     (errcode(ERRCODE_DATATYPE_MISMATCH),
+                      errmsg("table row type and query-specified row type do not match"),
+                      errdetail("Query has too many columns.")));
+         attr = resultDesc->attrs[attno++];
+
+         if (!attr->attisdropped)
+         {
+             /* Normal case: demand type match */
+             if (exprType((Node *) tle->expr) != attr->atttypid)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_DATATYPE_MISMATCH),
+                          errmsg("table row type and query-specified row type do not match"),
+                          errdetail("Table has type %s at ordinal position %d, but query expects %s.",
+                                    format_type_be(attr->atttypid),
+                                    attno,
+                              format_type_be(exprType((Node *) tle->expr)))));
+         }
+         else
+         {
+             /*
+              * For a dropped column, we can't check atttypid (it's likely 0).
+              * In any case the planner has most likely inserted an INT4 null.
+              * What we insist on is just *some* NULL constant.
+              */
+             if (!IsA(tle->expr, Const) ||
+                 !((Const *) tle->expr)->constisnull)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_DATATYPE_MISMATCH),
+                          errmsg("table row type and query-specified row type do not match"),
+                          errdetail("Query provides a value for a dropped column at ordinal position %d.",
+                                    attno)));
+         }
+     }
+     if (attno != resultDesc->natts)
+         ereport(ERROR,
+                 (errcode(ERRCODE_DATATYPE_MISMATCH),
+           errmsg("table row type and query-specified row type do not match"),
+                  errdetail("Query has too few columns.")));
+ }
+
+ static TupleTableSlot*
+ ExecProcessReturning(ProjectionInfo *projectReturning,
+                      TupleTableSlot *tupleSlot,
+                      TupleTableSlot *planSlot)
+ {
+     ExprContext *econtext = projectReturning->pi_exprContext;
+     TupleTableSlot *retSlot;
+
+     /*
+      * Reset per-tuple memory context to free any expression evaluation
+      * storage allocated in the previous cycle.
+      */
+     ResetExprContext(econtext);
+
+     /* Make tuple and any needed join variables available to ExecProject */
+     econtext->ecxt_scantuple = tupleSlot;
+     econtext->ecxt_outertuple = planSlot;
+
+     /* Compute the RETURNING expressions */
+     retSlot = ExecProject(projectReturning, NULL);
+
+     return retSlot;
+ }
+
+ static TupleTableSlot *
+ ExecInsert(TupleTableSlot *slot,
+             ItemPointer tupleid,
+             TupleTableSlot *planSlot,
+             EState *estate)
+ {
+     HeapTuple    tuple;
+     ResultRelInfo *resultRelInfo;
+     Relation    resultRelationDesc;
+     Oid            newId;
+     List        *recheckIndexes = NIL;
+
+     /*
+      * get the heap tuple out of the tuple table slot, making sure we have a
+      * writable copy
+      */
+     tuple = ExecMaterializeSlot(slot);
+
+     /*
+      * get information on the (current) result relation
+      */
+     resultRelInfo = estate->es_result_relations;
+     resultRelationDesc = resultRelInfo->ri_RelationDesc;
+
+     /*
+      * If the result relation has OIDs, force the tuple's OID to zero so that
+      * heap_insert will assign a fresh OID.  Usually the OID already will be
+      * zero at this point, but there are corner cases where the plan tree can
+      * return a tuple extracted literally from some table with the same
+      * rowtype.
+      *
+      * XXX if we ever wanted to allow users to assign their own OIDs to new
+      * rows, this'd be the place to do it.  For the moment, we make a point of
+      * doing this before calling triggers, so that a user-supplied trigger
+      * could hack the OID if desired.
+      */
+     if (resultRelationDesc->rd_rel->relhasoids)
+         HeapTupleSetOid(tuple, InvalidOid);
+
+     /* BEFORE ROW INSERT Triggers */
+     if (resultRelInfo->ri_TrigDesc &&
+         resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
+     {
+         HeapTuple    newtuple;
+
+         newtuple = ExecBRInsertTriggers(estate, resultRelInfo, tuple);
+
+         if (newtuple == NULL)    /* "do nothing" */
+             return NULL;
+
+         if (newtuple != tuple)    /* modified by Trigger(s) */
+         {
+             /*
+              * Put the modified tuple into a slot for convenience of routines
+              * below.  We assume the tuple was allocated in per-tuple memory
+              * context, and therefore will go away by itself. The tuple table
+              * slot should not try to clear it.
+              */
+             TupleTableSlot *newslot = estate->es_trig_tuple_slot;
+
+             if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor)
+                 ExecSetSlotDescriptor(newslot, slot->tts_tupleDescriptor);
+             ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
+             slot = newslot;
+             tuple = newtuple;
+         }
+     }
+
+     /*
+      * Check the constraints of the tuple
+      */
+     if (resultRelationDesc->rd_att->constr)
+         ExecConstraints(resultRelInfo, slot, estate);
+
+     /*
+      * insert the tuple
+      *
+      * Note: heap_insert returns the tid (location) of the new tuple in the
+      * t_self field.
+      */
+     newId = heap_insert(resultRelationDesc, tuple,
+                         estate->es_output_cid, 0, NULL);
+
+     IncrAppended();
+     (estate->es_processed)++;
+     estate->es_lastoid = newId;
+     setLastTid(&(tuple->t_self));
+
+     /*
+      * insert index entries for tuple
+      */
+     if (resultRelInfo->ri_NumIndices > 0)
+         recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
+
+     /* AFTER ROW INSERT Triggers */
+     ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
+
+     /* Process RETURNING if present */
+     if (resultRelInfo->ri_projectReturning)
+         slot = ExecProcessReturning(resultRelInfo->ri_projectReturning,
+                              slot, planSlot);
+
+     return slot;
+ }
+
+ /* ----------------------------------------------------------------
+  *        ExecDelete
+  *
+  *        DELETE is like UPDATE, except that we delete the tuple and no
+  *        index modifications are needed
+  * ----------------------------------------------------------------
+  */
+ static TupleTableSlot *
+ ExecDelete(ItemPointer tupleid,
+            TupleTableSlot *planSlot,
+            EState *estate)
+ {
+     ResultRelInfo* resultRelInfo;
+     Relation    resultRelationDesc;
+     HTSU_Result result;
+     ItemPointerData update_ctid;
+     TransactionId update_xmax;
+
+     /*
+      * get information on the (current) result relation
+      */
+     resultRelInfo = estate->es_result_relation_info;
+     resultRelationDesc = resultRelInfo->ri_RelationDesc;
+
+     /* BEFORE ROW DELETE Triggers */
+     if (resultRelInfo->ri_TrigDesc &&
+         resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0)
+     {
+         bool        dodelete;
+
+         dodelete = ExecBRDeleteTriggers(estate, resultRelInfo, tupleid);
+
+         if (!dodelete)            /* "do nothing" */
+             return planSlot;
+     }
+
+     /*
+      * delete the tuple
+      *
+      * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
+      * the row to be deleted is visible to that snapshot, and throw a can't-
+      * serialize error if not.    This is a special-case behavior needed for
+      * referential integrity updates in serializable transactions.
+      */
+ ldelete:;
+     result = heap_delete(resultRelationDesc, tupleid,
+                          &update_ctid, &update_xmax,
+                          estate->es_output_cid,
+                          estate->es_crosscheck_snapshot,
+                          true /* wait for commit */ );
+     switch (result)
+     {
+         case HeapTupleSelfUpdated:
+             /* already deleted by self; nothing to do */
+             return planSlot;
+
+         case HeapTupleMayBeUpdated:
+             break;
+
+         case HeapTupleUpdated:
+             if (IsXactIsoLevelSerializable)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+                          errmsg("could not serialize access due to concurrent update")));
+             else if (!ItemPointerEquals(tupleid, &update_ctid))
+             {
+                 TupleTableSlot *epqslot;
+
+                 epqslot = EvalPlanQual(estate,
+                                        resultRelInfo->ri_RangeTableIndex,
+                                        &update_ctid,
+                                        update_xmax);
+                 if (!TupIsNull(epqslot))
+                 {
+                     *tupleid = update_ctid;
+                     goto ldelete;
+                 }
+             }
+             /* tuple already deleted; nothing to do */
+             return planSlot;
+
+         default:
+             elog(ERROR, "unrecognized heap_delete status: %u", result);
+             return NULL;
+     }
+
+     IncrDeleted();
+     (estate->es_processed)++;
+
+     /*
+      * Note: Normally one would think that we have to delete index tuples
+      * associated with the heap tuple now...
+      *
+      * ... but in POSTGRES, we have no need to do this because VACUUM will
+      * take care of it later.  We can't delete index tuples immediately
+      * anyway, since the tuple is still visible to other transactions.
+      */
+
+     /* AFTER ROW DELETE Triggers */
+     ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+
+     /* Process RETURNING if present */
+     if (resultRelInfo->ri_projectReturning)
+     {
+         /*
+          * We have to put the target tuple into a slot, which means first we
+          * gotta fetch it.    We can use the trigger tuple slot.
+          */
+         TupleTableSlot *slot = estate->es_trig_tuple_slot;
+         HeapTupleData deltuple;
+         Buffer        delbuffer;
+
+         deltuple.t_self = *tupleid;
+         if (!heap_fetch(resultRelationDesc, SnapshotAny,
+                         &deltuple, &delbuffer, false, NULL))
+             elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
+
+         if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
+             ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
+         ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
+
+         planSlot = ExecProcessReturning(resultRelInfo->ri_projectReturning,
+                              slot, planSlot);
+
+         ExecClearTuple(slot);
+         ReleaseBuffer(delbuffer);
+     }
+
+     return planSlot;
+ }
+
+ /* ----------------------------------------------------------------
+  *        ExecUpdate
+  *
+  *        note: we can't run UPDATE queries with transactions
+  *        off because UPDATEs are actually INSERTs and our
+  *        scan will mistakenly loop forever, updating the tuple
+  *        it just inserted..    This should be fixed but until it
+  *        is, we don't want to get stuck in an infinite loop
+  *        which corrupts your database..
+  * ----------------------------------------------------------------
+  */
+ static TupleTableSlot *
+ ExecUpdate(TupleTableSlot *slot,
+            ItemPointer tupleid,
+            TupleTableSlot *planSlot,
+            EState *estate)
+ {
+     HeapTuple    tuple;
+     ResultRelInfo *resultRelInfo;
+     Relation    resultRelationDesc;
+     HTSU_Result result;
+     ItemPointerData update_ctid;
+     TransactionId update_xmax;
+     List *recheckIndexes = NIL;
+
+     /*
+      * abort the operation if not running transactions
+      */
+     if (IsBootstrapProcessingMode())
+         elog(ERROR, "cannot UPDATE during bootstrap");
+
+     /*
+      * get the heap tuple out of the tuple table slot, making sure we have a
+      * writable copy
+      */
+     tuple = ExecMaterializeSlot(slot);
+
+     /*
+      * get information on the (current) result relation
+      */
+     resultRelInfo = estate->es_result_relation_info;
+     resultRelationDesc = resultRelInfo->ri_RelationDesc;
+
+     /* BEFORE ROW UPDATE Triggers */
+     if (resultRelInfo->ri_TrigDesc &&
+         resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0)
+     {
+         HeapTuple    newtuple;
+
+         newtuple = ExecBRUpdateTriggers(estate, resultRelInfo,
+                                         tupleid, tuple);
+
+         if (newtuple == NULL)    /* "do nothing" */
+             return planSlot;
+
+         if (newtuple != tuple)    /* modified by Trigger(s) */
+         {
+             /*
+              * Put the modified tuple into a slot for convenience of routines
+              * below.  We assume the tuple was allocated in per-tuple memory
+              * context, and therefore will go away by itself. The tuple table
+              * slot should not try to clear it.
+              */
+             TupleTableSlot *newslot = estate->es_trig_tuple_slot;
+
+             if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor)
+                 ExecSetSlotDescriptor(newslot, slot->tts_tupleDescriptor);
+             ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
+             slot = newslot;
+             tuple = newtuple;
+         }
+     }
+
+     /*
+      * Check the constraints of the tuple
+      *
+      * If we generate a new candidate tuple after EvalPlanQual testing, we
+      * must loop back here and recheck constraints.  (We don't need to redo
+      * triggers, however.  If there are any BEFORE triggers then trigger.c
+      * will have done heap_lock_tuple to lock the correct tuple, so there's no
+      * need to do them again.)
+      */
+ lreplace:;
+     if (resultRelationDesc->rd_att->constr)
+         ExecConstraints(resultRelInfo, slot, estate);
+
+     /*
+      * replace the heap tuple
+      *
+      * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
+      * the row to be updated is visible to that snapshot, and throw a can't-
+      * serialize error if not.    This is a special-case behavior needed for
+      * referential integrity updates in serializable transactions.
+      */
+     result = heap_update(resultRelationDesc, tupleid, tuple,
+                          &update_ctid, &update_xmax,
+                          estate->es_output_cid,
+                          estate->es_crosscheck_snapshot,
+                          true /* wait for commit */ );
+     switch (result)
+     {
+         case HeapTupleSelfUpdated:
+             /* already deleted by self; nothing to do */
+             return planSlot;
+
+         case HeapTupleMayBeUpdated:
+             break;
+
+         case HeapTupleUpdated:
+             if (IsXactIsoLevelSerializable)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+                          errmsg("could not serialize access due to concurrent update")));
+             else if (!ItemPointerEquals(tupleid, &update_ctid))
+             {
+                 TupleTableSlot *epqslot;
+
+                 epqslot = EvalPlanQual(estate,
+                                        resultRelInfo->ri_RangeTableIndex,
+                                        &update_ctid,
+                                        update_xmax);
+                 if (!TupIsNull(epqslot))
+                 {
+                     *tupleid = update_ctid;
+                     slot = ExecFilterJunk(estate->es_result_relation_info->ri_junkFilter, epqslot);
+                     tuple = ExecMaterializeSlot(slot);
+                     goto lreplace;
+                 }
+             }
+             /* tuple already deleted; nothing to do */
+             return planSlot;
+
+         default:
+             elog(ERROR, "unrecognized heap_update status: %u", result);
+             return NULL;
+     }
+
+     IncrReplaced();
+     (estate->es_processed)++;
+
+     /*
+      * Note: instead of having to update the old index tuples associated with
+      * the heap tuple, all we do is form and insert new index tuples. This is
+      * because UPDATEs are actually DELETEs and INSERTs, and index tuple
+      * deletion is done later by VACUUM (see notes in ExecDelete).    All we do
+      * here is insert new index tuples.  -cim 9/27/89
+      */
+
+     /*
+      * insert index entries for tuple
+      *
+      * Note: heap_update returns the tid (location) of the new tuple in the
+      * t_self field.
+      *
+      * 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 */
+     ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
+                          recheckIndexes);
+
+     /* Process RETURNING if present */
+     if (resultRelInfo->ri_projectReturning)
+         slot = ExecProcessReturning(resultRelInfo->ri_projectReturning,
+                                     slot, planSlot);
+
+     return slot;
+ }
+
+ TupleTableSlot *
+ ExecDml(DmlState *node)
+ {
+     CmdType operation = node->operation;
+     EState *estate = node->ps.state;
+     JunkFilter *junkfilter;
+     TupleTableSlot *slot;
+     TupleTableSlot *planSlot;
+     ItemPointer tupleid = NULL;
+     ItemPointerData tuple_ctid;
+
+     for (;;)
+     {
+         planSlot = ExecProcNode(node->dmlplans[node->ds_whichplan]);
+         if (TupIsNull(planSlot))
+         {
+             node->ds_whichplan++;
+             if (node->ds_whichplan < node->ds_nplans)
+             {
+                 estate->es_result_relation_info++;
+                 continue;
+             }
+             else
+                 return NULL;
+         }
+         else
+             break;
+     }
+
+     slot = planSlot;
+
+     if ((junkfilter = estate->es_result_relation_info->ri_junkFilter) != NULL)
+     {
+         /*
+          * extract the 'ctid' junk attribute.
+          */
+         if (operation == CMD_UPDATE || operation == CMD_DELETE)
+         {
+             Datum        datum;
+             bool        isNull;
+
+             datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
+                                              &isNull);
+             /* shouldn't ever get a null result... */
+             if (isNull)
+                 elog(ERROR, "ctid is NULL");
+
+             tupleid = (ItemPointer) DatumGetPointer(datum);
+             tuple_ctid = *tupleid;    /* make sure we don't free the ctid!! */
+             tupleid = &tuple_ctid;
+         }
+
+         if (operation != CMD_DELETE)
+             slot = ExecFilterJunk(junkfilter, slot);
+     }
+
+     switch (operation)
+     {
+         case CMD_INSERT:
+             return ExecInsert(slot, tupleid, planSlot, estate);
+             break;
+         case CMD_UPDATE:
+             return ExecUpdate(slot, tupleid, planSlot, estate);
+             break;
+         case CMD_DELETE:
+             return ExecDelete(tupleid, slot, estate);
+         default:
+             elog(ERROR, "unknown operation");
+             break;
+     }
+
+     return NULL;
+ }
+
+ DmlState *
+ ExecInitDml(Dml *node, EState *estate, int eflags)
+ {
+     DmlState *dmlstate;
+     ResultRelInfo *resultRelInfo;
+     Plan *subplan;
+     ListCell *l;
+     ListCell *relindex;
+     CmdType operation = node->operation;
+     int i;
+
+     TupleDesc tupDesc;
+
+     /*
+      * create state structure
+      */
+     dmlstate = makeNode(DmlState);
+     dmlstate->ps.plan = (Plan *) node;
+     dmlstate->ps.state = estate;
+     dmlstate->ps.targetlist = node->plan.targetlist;
+
+     dmlstate->ds_nplans = list_length(node->plans);
+     dmlstate->dmlplans = (PlanState **) palloc0(sizeof(PlanState *) * dmlstate->ds_nplans);
+     dmlstate->operation = node->operation;
+
+     estate->es_result_relation_info = estate->es_result_relations;
+     relindex = list_head(node->resultRelations);
+     i = 0;
+     foreach(l, node->plans)
+     {
+         subplan = lfirst(l);
+
+         dmlstate->dmlplans[i] = ExecInitNode(subplan, estate, eflags);
+
+         i++;
+         estate->es_result_relation_info++;
+         relindex = lnext(relindex);
+     }
+
+     estate->es_result_relation_info = estate->es_result_relations;
+
+     dmlstate->ds_whichplan = 0;
+
+     subplan = (Plan *) linitial(node->plans);
+
+     /* Initialize targetlist for RETURNING */
+     if (node->returningLists)
+     {
+         TupleTableSlot *slot;
+         ExprContext *econtext;
+
+         /*
+          * Initialize result tuple slot and assign
+          * type from the target list.
+          */
+         tupDesc = ExecTypeFromTL((List *) linitial(node->returningLists),
+                                  false);
+
+         /*
+          * Set up a slot for the output of the RETURNING projection(s).
+          */
+         slot = ExecAllocTableSlot(estate->es_tupleTable);
+         ExecSetSlotDescriptor(slot, tupDesc);
+
+         econtext = CreateExprContext(estate);
+
+         Assert(list_length(node->returningLists) == estate->es_num_result_relations);
+         resultRelInfo = estate->es_result_relations;
+         foreach(l, node->returningLists)
+         {
+             List       *rlist = (List *) lfirst(l);
+             List       *rliststate;
+
+             rliststate = (List *) ExecInitExpr((Expr *) rlist, &dmlstate->ps);
+             resultRelInfo->ri_projectReturning =
+                 ExecBuildProjectionInfo(rliststate, econtext, slot,
+                                         resultRelInfo->ri_RelationDesc->rd_att);
+             resultRelInfo++;
+         }
+
+         dmlstate->ps.targetlist = estate->es_result_relation_info->ri_projectReturning->pi_targetlist;
+         dmlstate->ps.ps_ResultTupleSlot = slot;
+         dmlstate->ps.ps_ExprContext = econtext;
+     }
+     else
+     {
+         ExecInitResultTupleSlot(estate, &dmlstate->ps);
+         tupDesc = ExecTypeFromTL(subplan->targetlist, false);
+         ExecAssignResultType(&dmlstate->ps, tupDesc);
+
+         dmlstate->ps.targetlist = subplan->targetlist;
+         dmlstate->ps.ps_ExprContext = NULL;
+     }
+
+     /*
+      * Initialize the junk filter if needed. INSERT queries need a filter
+      * if there are any junk attrs in the tlist.  UPDATE and DELETE
+      * always need a filter, since there's always a junk 'ctid' attribute
+      * present --- no need to look first.
+      *
+      * This section of code is also a convenient place to verify that the
+      * output of an INSERT or UPDATE matches the target table(s).
+      */
+     {
+         bool        junk_filter_needed = false;
+         ListCell   *tlist;
+
+         switch (operation)
+         {
+             case CMD_INSERT:
+                 foreach(tlist, subplan->targetlist)
+                 {
+                     TargetEntry *tle = (TargetEntry *) lfirst(tlist);
+
+                     if (tle->resjunk)
+                     {
+                         junk_filter_needed = true;
+                         break;
+                     }
+                 }
+                 break;
+             case CMD_UPDATE:
+             case CMD_DELETE:
+                 junk_filter_needed = true;
+                 break;
+             default:
+                 break;
+         }
+
+         if (junk_filter_needed)
+         {
+             /*
+              * If there are multiple result relations, each one needs its own
+              * junk filter.  Note this is only possible for UPDATE/DELETE, so
+              * we can't be fooled by some needing a filter and some not.
+
+              */
+             if (dmlstate->ds_nplans > 1)
+             {
+                 resultRelInfo = estate->es_result_relations;
+                 for (i = 0; i < dmlstate->ds_nplans; i++)
+                 {
+                     PlanState *ps = dmlstate->dmlplans[i];
+                     JunkFilter *j;
+
+                     if (operation == CMD_UPDATE)
+                         ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+                                             ps->plan->targetlist);
+
+                     j = ExecInitJunkFilter(ps->plan->targetlist,
+                             resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
+                                 ExecAllocTableSlot(estate->es_tupleTable));
+
+
+                     /*
+                      * Since it must be UPDATE/DELETE, there had better be a
+                      * "ctid" junk attribute in the tlist ... but ctid could
+                      * be at a different resno for each result relation. We
+                      * look up the ctid resnos now and save them in the
+                      * junkfilters.
+                      */
+                     j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+                     if (!AttributeNumberIsValid(j->jf_junkAttNo))
+                         elog(ERROR, "could not find junk ctid column");
+                     resultRelInfo->ri_junkFilter = j;
+                     resultRelInfo++;
+                 }
+             }
+             else
+             {
+                 JunkFilter *j;
+                 subplan = dmlstate->dmlplans[0]->plan;
+
+                 if (operation == CMD_INSERT || operation == CMD_UPDATE)
+                     ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc,
+                                         subplan->targetlist);
+
+                 j = ExecInitJunkFilter(subplan->targetlist,
estate->es_result_relation_info->ri_RelationDesc->rd_att->tdhasoid,
+                                        ExecAllocTableSlot(estate->es_tupleTable));
+
+                 if (operation == CMD_UPDATE || operation == CMD_DELETE)
+                 {
+                     /* FOR UPDATE/DELETE, find the ctid junk attr now */
+                     j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+                     if (!AttributeNumberIsValid(j->jf_junkAttNo))
+                         elog(ERROR, "could not find junk ctid column");
+                 }
+
+                 estate->es_result_relation_info->ri_junkFilter = j;
+             }
+         }
+         else
+         {
+             if (operation == CMD_INSERT)
+                 ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc,
+                         ((Plan *) linitial(((Dml *) dmlstate->ps.plan)->plans))->targetlist);
+         }
+     }
+
+     return dmlstate;
+ }
+
+ int
+ ExecCountSlotsDml(Dml *node)
+ {
+     ListCell* l;
+     int nslots = 5;
+
+     foreach(l, node->plans)
+         nslots += ExecCountSlotsNode((Plan *) lfirst(l));
+
+     return nslots;
+ }
+
+ void
+ ExecEndDml(DmlState *node)
+ {
+     int i;
+
+     /*
+      * Free the exprcontext
+      */
+     ExecFreeExprContext(&node->ps);
+
+     /*
+      * clean out the tuple table
+      */
+     ExecClearTuple(node->ps.ps_ResultTupleSlot);
+
+     /*
+      * shut down subplans
+      */
+     for (i=0;i<node->ds_nplans;++i)
+     {
+         ExecEndNode(node->dmlplans[i]);
+     }
+
+     pfree(node->dmlplans);
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 171,177 **** _copyAppend(Append *from)
       * copy remainder of node
       */
      COPY_NODE_FIELD(appendplans);
-     COPY_SCALAR_FIELD(isTarget);

      return newnode;
  }
--- 171,176 ----
***************
*** 1391,1396 **** _copyXmlExpr(XmlExpr *from)
--- 1390,1411 ----

      return newnode;
  }
+
+ static Dml *
+ _copyDml(Dml *from)
+ {
+     Dml    *newnode = makeNode(Dml);
+
+     CopyPlanFields((Plan *) from, (Plan *) newnode);
+
+     COPY_NODE_FIELD(plans);
+     COPY_SCALAR_FIELD(operation);
+     COPY_NODE_FIELD(resultRelations);
+     COPY_NODE_FIELD(returningLists);
+
+     return newnode;
+ }
+

  /*
   * _copyNullIfExpr (same as OpExpr)
***************
*** 4083,4088 **** copyObject(void *from)
--- 4098,4106 ----
          case T_XmlSerialize:
              retval = _copyXmlSerialize(from);
              break;
+         case T_Dml:
+             retval = _copyDml(from);
+             break;

          default:
              elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 326,332 **** _outAppend(StringInfo str, Append *node)
      _outPlanInfo(str, (Plan *) node);

      WRITE_NODE_FIELD(appendplans);
-     WRITE_BOOL_FIELD(isTarget);
  }

  static void
--- 326,331 ----
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 574,580 **** create_append_plan(PlannerInfo *root, AppendPath *best_path)
          subplans = lappend(subplans, create_plan(root, subpath));
      }

!     plan = make_append(subplans, false, tlist);

      return (Plan *) plan;
  }
--- 574,580 ----
          subplans = lappend(subplans, create_plan(root, subpath));
      }

!     plan = make_append(subplans, tlist);

      return (Plan *) plan;
  }
***************
*** 2616,2622 **** make_worktablescan(List *qptlist,
  }

  Append *
! make_append(List *appendplans, bool isTarget, List *tlist)
  {
      Append       *node = makeNode(Append);
      Plan       *plan = &node->plan;
--- 2616,2622 ----
  }

  Append *
! make_append(List *appendplans, List *tlist)
  {
      Append       *node = makeNode(Append);
      Plan       *plan = &node->plan;
***************
*** 2652,2658 **** make_append(List *appendplans, bool isTarget, List *tlist)
      plan->lefttree = NULL;
      plan->righttree = NULL;
      node->appendplans = appendplans;
-     node->isTarget = isTarget;

      return node;
  }
--- 2652,2657 ----
***************
*** 3659,3664 **** make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount,
--- 3658,3689 ----
      return node;
  }

+ Dml *
+ make_dml(List *subplans, List *returningLists, List *resultRelations, CmdType operation)
+ {
+     Dml *node = makeNode(Dml);
+
+     Assert(list_length(subplans) == list_length(resultRelations));
+     Assert(!returningLists || list_length(returningLists) == list_length(resultRelations));
+
+     node->plan.lefttree = NULL;
+     node->plan.righttree = NULL;
+     node->plan.qual = NIL;
+
+     if (returningLists)
+         node->plan.targetlist = linitial(returningLists);
+     else
+         node->plan.targetlist = NIL;
+
+     node->plans = subplans;
+     node->resultRelations = resultRelations;
+     node->returningLists = returningLists;
+
+     node->operation = operation;
+
+     return node;
+ }
+
  /*
   * make_result
   *      Build a Result plan node
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 478,485 **** subquery_planner(PlannerGlobal *glob, Query *parse,
--- 478,494 ----
          rt_fetch(parse->resultRelation, parse->rtable)->inh)
          plan = inheritance_planner(root);
      else
+     {
          plan = grouping_planner(root, tuple_fraction);

+         if (parse->commandType != CMD_SELECT)
+             plan = (Plan *) make_dml(list_make1(plan),
+                                      root->returningLists,
+                                      root->resultRelations,
+                                      parse->commandType);
+     }
+
+
      /*
       * If any subplans were generated, or if we're inside a subplan, build
       * initPlan list and extParam/allParam sets for plan nodes, and attach the
***************
*** 624,632 **** preprocess_qual_conditions(PlannerInfo *root, Node *jtnode)
   * is an inheritance set. Source inheritance is expanded at the bottom of the
   * plan tree (see allpaths.c), but target inheritance has to be expanded at
   * the top.  The reason is that for UPDATE, each target relation needs a
!  * different targetlist matching its own column set.  Also, for both UPDATE
!  * and DELETE, the executor needs the Append plan node at the top, else it
!  * can't keep track of which table is the current target table.  Fortunately,
   * the UPDATE/DELETE target can never be the nullable side of an outer join,
   * so it's OK to generate the plan this way.
   *
--- 633,639 ----
   * is an inheritance set. Source inheritance is expanded at the bottom of the
   * plan tree (see allpaths.c), but target inheritance has to be expanded at
   * the top.  The reason is that for UPDATE, each target relation needs a
!  * different targetlist matching its own column set.  Fortunately,
   * the UPDATE/DELETE target can never be the nullable side of an outer join,
   * so it's OK to generate the plan this way.
   *
***************
*** 737,747 **** inheritance_planner(PlannerInfo *root)
       */
      parse->rtable = rtable;

!     /* Suppress Append if there's only one surviving child rel */
!     if (list_length(subplans) == 1)
!         return (Plan *) linitial(subplans);
!
!     return (Plan *) make_append(subplans, true, tlist);
  }

  /*--------------------
--- 744,753 ----
       */
      parse->rtable = rtable;

!     return (Plan *) make_dml(subplans,
!                              root->returningLists,
!                              root->resultRelations,
!                              parse->commandType);
  }

  /*--------------------
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 375,380 **** set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
--- 375,403 ----
              set_join_references(glob, (Join *) plan, rtoffset);
              break;

+         case T_Dml:
+             {
+                 /*
+                  * grouping_planner() already called set_returning_clause_references
+                  * so the targetList's references are already set.
+                  */
+                 Dml *splan = (Dml *) plan;
+
+                 foreach(l, splan->resultRelations)
+                 {
+                     lfirst_int(l) += rtoffset;
+                 }
+
+                 Assert(splan->plan.qual == NIL);
+                 foreach(l, splan->plans)
+                 {
+                     lfirst(l) = set_plan_refs(glob,
+                                               (Plan *) lfirst(l),
+                                               rtoffset);
+                 }
+             }
+             break;
+
          case T_Hash:
          case T_Material:
          case T_Sort:
*** a/src/backend/optimizer/plan/subselect.c
--- b/src/backend/optimizer/plan/subselect.c
***************
*** 2034,2039 **** finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params)
--- 2034,2040 ----
          case T_Unique:
          case T_SetOp:
          case T_Group:
+         case T_Dml:
              break;

          default:
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 448,454 **** generate_union_plan(SetOperationStmt *op, PlannerInfo *root,
      /*
       * Append the child results together.
       */
!     plan = (Plan *) make_append(planlist, false, tlist);

      /*
       * For UNION ALL, we just need the Append plan.  For UNION, need to add
--- 448,454 ----
      /*
       * Append the child results together.
       */
!     plan = (Plan *) make_append(planlist, tlist);

      /*
       * For UNION ALL, we just need the Append plan.  For UNION, need to add
***************
*** 539,545 **** generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root,
      /*
       * Append the child results together.
       */
!     plan = (Plan *) make_append(planlist, false, tlist);

      /* Identify the grouping semantics */
      groupList = generate_setop_grouplist(op, tlist);
--- 539,545 ----
      /*
       * Append the child results together.
       */
!     plan = (Plan *) make_append(planlist, tlist);

      /* Identify the grouping semantics */
      groupList = generate_setop_grouplist(op, tlist);
*** /dev/null
--- b/src/include/executor/nodeDml.h
***************
*** 0 ****
--- 1,11 ----
+ #ifndef NODEDML_H
+ #define NODEDML_H
+
+ #include "nodes/execnodes.h"
+
+ extern int ExecCountSlotsDml(Dml *node);
+ extern DmlState *ExecInitDml(Dml *node, EState *estate, int eflags);
+ extern TupleTableSlot *ExecDml(DmlState *node);
+ extern void ExecEndDml(DmlState *node);
+
+ #endif
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 976,981 **** typedef struct ResultState
--- 976,996 ----
  } ResultState;

  /* ----------------
+  *     DmlState information
+  * ----------------
+  */
+ typedef struct DmlState
+ {
+     PlanState        ps;                /* its first field is NodeTag */
+     PlanState      **dmlplans;
+     int                ds_nplans;
+     int                ds_whichplan;
+
+     CmdType            operation;
+ } DmlState;
+
+
+ /* ----------------
   *     AppendState information
   *
   *        nplans            how many plans are in the list
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 71,76 **** typedef enum NodeTag
--- 71,77 ----
      T_Hash,
      T_SetOp,
      T_Limit,
+     T_Dml,
      /* this one isn't a subclass of Plan: */
      T_PlanInvalItem,

***************
*** 190,195 **** typedef enum NodeTag
--- 191,197 ----
      T_NullTestState,
      T_CoerceToDomainState,
      T_DomainConstraintState,
+     T_DmlState,

      /*
       * TAGS FOR PLANNER NODES (relation.h)
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 164,185 **** typedef struct Result
      Node       *resconstantqual;
  } Result;

  /* ----------------
   *     Append node -
   *        Generate the concatenation of the results of sub-plans.
-  *
-  * Append nodes are sometimes used to switch between several result relations
-  * (when the target of an UPDATE or DELETE is an inheritance set).    Such a
-  * node will have isTarget true.  The Append executor is then responsible
-  * for updating the executor state to point at the correct target relation
-  * whenever it switches subplans.
   * ----------------
   */
  typedef struct Append
  {
      Plan        plan;
      List       *appendplans;
-     bool        isTarget;
  } Append;

  /* ----------------
--- 164,189 ----
      Node       *resconstantqual;
  } Result;

+ typedef struct Dml
+ {
+     Plan        plan;
+
+     CmdType        operation;
+     List       *plans;
+     List       *resultRelations;
+     List       *returningLists;
+ } Dml;
+
+
  /* ----------------
   *     Append node -
   *        Generate the concatenation of the results of sub-plans.
   * ----------------
   */
  typedef struct Append
  {
      Plan        plan;
      List       *appendplans;
  } Append;

  /* ----------------
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
***************
*** 41,47 **** extern Plan *optimize_minmax_aggregates(PlannerInfo *root, List *tlist,
  extern Plan *create_plan(PlannerInfo *root, Path *best_path);
  extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual,
                    Index scanrelid, Plan *subplan, List *subrtable);
! extern Append *make_append(List *appendplans, bool isTarget, List *tlist);
  extern RecursiveUnion *make_recursive_union(List *tlist,
                       Plan *lefttree, Plan *righttree, int wtParam,
                       List *distinctList, long numGroups);
--- 41,47 ----
  extern Plan *create_plan(PlannerInfo *root, Path *best_path);
  extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual,
                    Index scanrelid, Plan *subplan, List *subrtable);
! extern Append *make_append(List *appendplans, List *tlist);
  extern RecursiveUnion *make_recursive_union(List *tlist,
                       Plan *lefttree, Plan *righttree, int wtParam,
                       List *distinctList, long numGroups);
***************
*** 69,74 **** extern Plan *materialize_finished_plan(Plan *subplan);
--- 69,76 ----
  extern Unique *make_unique(Plan *lefttree, List *distinctList);
  extern Limit *make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount,
             int64 offset_est, int64 count_est);
+ extern Dml *make_dml(List *subplans, List *returningLists, List *resultRelation,
+             CmdType operation);
  extern SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
             List *distinctList, AttrNumber flagColIdx, int firstFlag,
             long numGroups, double outputRows);

pgsql-hackers by date:

Previous
From: Adriano Lange
Date:
Subject: Re: Memory context usage
Next
From: Greg Stark
Date:
Subject: Re: LWLock Queue Jumping