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

From Marko Tiikkaja
Subject Re: Using results from INSERT ... RETURNING
Date
Msg-id 4AC0D6EF.70001@cs.helsinki.fi
Whole thread Raw
In response to Re: Using results from INSERT ... RETURNING  (Robert Haas <robertmhaas@gmail.com>)
Responses Re: Using results from INSERT ... RETURNING
Re: Using results from INSERT ... RETURNING
List pgsql-hackers
Robert Haas wrote:
> So I think we should at a minimum ask the patch author to (1) fix the
> explain bugs I found and (2) update the README, as well as (3) revert
> needless whitespace changes - there are a couple in execMain.c, from
> the looks of it.

In the attached patch, I made the changes to explain as you suggested
and reverted the only whitespace change I could find from execMain.c.
However, English isn't my first language so I'm not very confident about
fixing the README.

Regards,
Marko Tiikkaja
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 581,586 **** ExplainNode(Plan *plan, PlanState *planstate,
--- 581,587 ----
      const char *pname;            /* node type name for text output */
      const char *sname;            /* node type name for non-text output */
      const char *strategy = NULL;
+     const char *operation = NULL; /* DML operation */
      int            save_indent = es->indent;
      bool        haschildren;

***************
*** 705,710 **** ExplainNode(Plan *plan, PlanState *planstate,
--- 706,729 ----
          case T_Hash:
              pname = sname = "Hash";
              break;
+         case T_Dml:
+             sname = "Dml";
+             switch( ((Dml *) plan)->operation)
+             {
+                 case CMD_INSERT:
+                     pname = operation = "Insert";
+                     break;
+                 case CMD_UPDATE:
+                     pname = operation = "Update";
+                     break;
+                 case CMD_DELETE:
+                     pname = operation = "Delete";
+                     break;
+                 default:
+                     pname = "???";
+                     break;
+             }
+             break;
          default:
              pname = sname = "???";
              break;
***************
*** 740,745 **** ExplainNode(Plan *plan, PlanState *planstate,
--- 759,766 ----
              ExplainPropertyText("Parent Relationship", relationship, es);
          if (plan_name)
              ExplainPropertyText("Subplan Name", plan_name, es);
+         if (operation)
+             ExplainPropertyText("Operation", operation, es);
      }

      switch (nodeTag(plan))
***************
*** 1064,1069 **** ExplainNode(Plan *plan, PlanState *planstate,
--- 1085,1095 ----
                                 ((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 ----
***************
*** 814,909 **** 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,
-                                            ExecInitExtraTupleSlot(estate));
-
-                     /*
-                      * 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
--- 800,828 ----
      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
***************
*** 916,928 **** 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,
                                         ExecInitExtraTupleSlot(estate));
--- 835,842 ----
***************
*** 930,947 **** 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)
--- 844,851 ----
                  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)
***************
*** 971,1027 **** 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 = ExecInitExtraTupleSlot(estate);
-         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;

--- 875,886 ----
***************
*** 1123,1197 **** 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,
--- 982,987 ----
***************
*** 1423,1430 **** ExecutePlan(EState *estate,
      JunkFilter *junkfilter;
      TupleTableSlot *planSlot;
      TupleTableSlot *slot;
-     ItemPointer tupleid = NULL;
-     ItemPointerData tuple_ctid;
      long        current_tuple_count;

      /*
--- 1213,1218 ----
***************
*** 1495,1501 **** 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.
--- 1283,1289 ----
           *
           * 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.
***************
*** 1604,1635 **** 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);
          }

          /*
--- 1392,1398 ----
                  }
              }

!             slot = ExecFilterJunk(junkfilter, slot);
          }

          /*
***************
*** 1644,1658 **** 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:
--- 1407,1416 ----
                  break;

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

              default:
***************
*** 1708,2127 **** 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
   */
--- 1466,1471 ----
***************
*** 2222,2263 **** 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.
   *
--- 1566,1571 ----
*** a/src/backend/executor/execProcnode.c
--- b/src/backend/executor/execProcnode.c
***************
*** 90,95 ****
--- 90,96 ----
  #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"
***************
*** 285,290 **** ExecInitNode(Plan *node, EState *estate, int eflags)
--- 286,296 ----
                                                   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 */
***************
*** 450,455 **** ExecProcNode(PlanState *node)
--- 456,465 ----
              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;
***************
*** 666,671 **** ExecEndNode(PlanState *node)
--- 676,685 ----
              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,834 ----
+ #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;
+     CmdType operation = node->operation;
+     int nplans;
+     int i;
+
+     TupleDesc tupDesc;
+
+     nplans = list_length(node->plans);
+
+     /*
+      * Do we want to scan just one subplan?  (Special case for EvalPlanQual)
+      * XXX pretty dirty way of determining that this case applies ...
+      */
+     if (estate->es_evTuple != NULL)
+     {
+         int tplan;
+
+         tplan = estate->es_result_relation_info - estate->es_result_relations;
+         Assert(tplan >= 0 && tplan < nplans);
+
+         /*
+          * We don't want another DmlNode on top, so just
+          * return a PlanState for the subplan wanted.
+          */
+         return (DmlState *) ExecInitNode(list_nth(node->plans, tplan), estate, eflags);
+     }
+
+     /*
+      * create state structure
+      */
+     dmlstate = makeNode(DmlState);
+     dmlstate->ps.plan = (Plan *) node;
+     dmlstate->ps.state = estate;
+     dmlstate->ps.targetlist = node->plan.targetlist;
+
+     dmlstate->ds_nplans = nplans;
+     dmlstate->dmlplans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
+     dmlstate->operation = node->operation;
+
+     estate->es_result_relation_info = estate->es_result_relations;
+     i = 0;
+     foreach(l, node->plans)
+     {
+         subplan = lfirst(l);
+
+         dmlstate->dmlplans[i] = ExecInitNode(subplan, estate, eflags);
+
+         i++;
+         estate->es_result_relation_info++;
+     }
+
+     estate->es_result_relation_info = estate->es_result_relations;
+
+     dmlstate->ds_whichplan = 0;
+
+     subplan = (Plan *) linitial(node->plans);
+
+     if (node->returningLists)
+     {
+         TupleTableSlot *slot;
+         ExprContext *econtext;
+
+         /*
+          * Initialize result tuple slot and assign
+          * type from the RETURNING 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.ps_ResultTupleSlot = slot;
+         dmlstate->ps.ps_ExprContext = econtext;
+     }
+     else
+     {
+         ExecInitResultTupleSlot(estate, &dmlstate->ps);
+         tupDesc = ExecTypeFromTL(subplan->targetlist, false);
+         ExecAssignResultType(&dmlstate->ps, tupDesc);
+
+         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;
+         }
+
+         resultRelInfo = estate->es_result_relations;
+
+         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 (nplans > 1)
+             {
+                 for (i = 0; i < 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,
+                                 ExecInitExtraTupleSlot(estate));
+
+
+                     /*
+                      * 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(resultRelInfo->ri_RelationDesc,
+                                         subplan->targetlist);
+
+                 j = ExecInitJunkFilter(subplan->targetlist,
+                                        resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
+                                        ExecInitExtraTupleSlot(estate));
+
+                 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,
+                                     subplan->targetlist);
+         }
+     }
+
+     return dmlstate;
+ }
+
+ 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)
***************
*** 4097,4102 **** copyObject(void *from)
--- 4112,4120 ----
          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
***************
*** 579,585 **** create_append_plan(PlannerInfo *root, AppendPath *best_path)
          subplans = lappend(subplans, create_plan(root, subpath));
      }

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

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

!     plan = make_append(subplans, tlist);

      return (Plan *) plan;
  }
***************
*** 2621,2627 **** make_worktablescan(List *qptlist,
  }

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

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

      return node;
  }
--- 2657,2662 ----
***************
*** 3665,3670 **** make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount,
--- 3664,3720 ----
      return node;
  }

+ Dml *
+ make_dml(List *subplans, List *returningLists, List *resultRelations, CmdType operation)
+ {
+     Dml *node = makeNode(Dml);
+     Plan       *plan = &node->plan;
+     double        total_size;
+     ListCell   *subnode;
+
+     Assert(list_length(subplans) == list_length(resultRelations));
+     Assert(!returningLists || list_length(returningLists) == list_length(resultRelations));
+
+     /*
+      * Compute cost as sum of subplan costs.
+      */
+     plan->startup_cost = 0;
+     plan->total_cost = 0;
+     plan->plan_rows = 0;
+     total_size = 0;
+     foreach(subnode, subplans)
+     {
+         Plan       *subplan = (Plan *) lfirst(subnode);
+
+         if (subnode == list_head(subplans))    /* first node? */
+             plan->startup_cost = subplan->startup_cost;
+         plan->total_cost += subplan->total_cost;
+         plan->plan_rows += subplan->plan_rows;
+         total_size += subplan->plan_width * subplan->plan_rows;
+     }
+     if (plan->plan_rows > 0)
+         plan->plan_width = rint(total_size / plan->plan_rows);
+     else
+         plan->plan_width = 0;
+
+     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
***************
*** 2018,2023 **** finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params)
--- 2018,2024 ----
          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,10 ----
+ #ifndef NODEDML_H
+ #define NODEDML_H
+
+ #include "nodes/execnodes.h"
+
+ 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: Tom Lane
Date:
Subject: Re: Rejecting weak passwords
Next
From: Tom Lane
Date:
Subject: Re: Rejecting weak passwords