Re: Using results from INSERT ... RETURNING - Mailing list pgsql-hackers
| From | Marko Tiikkaja |
|---|---|
| Subject | Re: Using results from INSERT ... RETURNING |
| Date | |
| Msg-id | [email protected] Whole thread Raw |
| In response to | Re: Using results from INSERT ... RETURNING (Robert Haas <[email protected]>) |
| 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: