*** 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/README --- b/src/backend/executor/README *************** *** 25,38 **** There is a moderately intelligent scheme to avoid rescanning nodes unnecessarily (for example, Sort does not rescan its input if no parameters of the input have changed, since it can just reread its stored sorted data). ! The plan tree concept implements SELECT directly: it is only necessary to ! deliver the top-level result tuples to the client, or insert them into ! another table in the case of INSERT ... SELECT. (INSERT ... VALUES is ! handled similarly, but the plan tree is just a Result node with no source ! tables.) For UPDATE, the plan tree selects the tuples that need to be ! updated (WHERE condition) and delivers a new calculated tuple value for each ! such tuple, plus a "junk" (hidden) tuple CTID identifying the target tuple. ! The executor's top level then uses this information to update the correct tuple. DELETE is similar to UPDATE except that only a CTID need be delivered by the plan tree. --- 25,42 ---- unnecessarily (for example, Sort does not rescan its input if no parameters of the input have changed, since it can just reread its stored sorted data). ! It is only necessary to deliver the top-level result tuples to the client. ! If the query is a SELECT, the topmost plan node is the output of the SELECT. ! If the query is a DML operation, a DML node is added to the top, which calls ! its child nodes to fetch the tuples. If the DML operation has a RETURNING ! clause the node will output the projected tuples, otherwise it gives out ! dummy tuples until it has processed all tuples from its child nodes. After ! that, it gives NULL. ! ! Handling INSERT is pretty straightforward: the tuples returned from the ! subtree are inserted into the correct result relation. In addition to the ! tuple value, UPDATE needs a "junk" (hidden) tuple CTID identifying the ! target tuple. The DML node needs this information to update the correct tuple. DELETE is similar to UPDATE except that only a CTID need be delivered by the plan tree. *** 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,947 **** 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)); estate->es_junkFilter = j; - 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) --- 835,849 ---- } else { JunkFilter *j; j = ExecInitJunkFilter(planstate->plan->targetlist, tupType->tdhasoid, ExecInitExtraTupleSlot(estate)); estate->es_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; --- 873,884 ---- *************** *** 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, --- 980,985 ---- *************** *** 1423,1430 **** ExecutePlan(EState *estate, JunkFilter *junkfilter; TupleTableSlot *planSlot; TupleTableSlot *slot; - ItemPointer tupleid = NULL; - ItemPointerData tuple_ctid; long current_tuple_count; /* --- 1211,1216 ---- *************** *** 1438,1462 **** ExecutePlan(EState *estate, estate->es_direction = direction; /* - * Process BEFORE EACH STATEMENT triggers - */ - switch (operation) - { - case CMD_UPDATE: - ExecBSUpdateTriggers(estate, estate->es_result_relation_info); - break; - case CMD_DELETE: - ExecBSDeleteTriggers(estate, estate->es_result_relation_info); - break; - case CMD_INSERT: - ExecBSInsertTriggers(estate, estate->es_result_relation_info); - break; - default: - /* do nothing */ - break; - } - - /* * Loop until we've processed the proper number of tuples from the plan. */ for (;;) --- 1224,1229 ---- *************** *** 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. --- 1262,1268 ---- * * 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); } /* --- 1371,1377 ---- } } ! 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: --- 1386,1395 ---- break; case CMD_INSERT: case CMD_DELETE: case CMD_UPDATE: ! if (estate->es_plannedstmt->returningLists) ! (*dest->receiveSlot) (slot, dest); break; default: *************** *** 1670,1694 **** lnext: ; if (numberTuples && numberTuples == current_tuple_count) break; } - - /* - * Process AFTER EACH STATEMENT triggers - */ - switch (operation) - { - case CMD_UPDATE: - ExecASUpdateTriggers(estate, estate->es_result_relation_info); - break; - case CMD_DELETE: - ExecASDeleteTriggers(estate, estate->es_result_relation_info); - break; - case CMD_INSERT: - ExecASInsertTriggers(estate, estate->es_result_relation_info); - break; - default: - /* do nothing */ - break; - } } /* ---------------------------------------------------------------- --- 1407,1412 ---- *************** *** 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 */ --- 1426,1431 ---- *************** *** 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. * --- 1526,1531 ---- *** 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,891 ---- + #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; + } + + + static void fireBSTriggers(DmlState *node) + { + /* + * Process BEFORE EACH STATEMENT triggers + */ + switch (node->operation) + { + case CMD_UPDATE: + ExecBSUpdateTriggers(node->ps.state, node->ps.state->es_result_relation_info); + break; + case CMD_DELETE: + ExecBSDeleteTriggers(node->ps.state, node->ps.state->es_result_relation_info); + break; + case CMD_INSERT: + ExecBSInsertTriggers(node->ps.state, node->ps.state->es_result_relation_info); + break; + default: + elog(ERROR, "unknown operation"); + break; + } + } + + + static void fireASTriggers(DmlState *node) + { + /* + * Process AFTER EACH STATEMENT triggers + */ + switch (node->operation) + { + case CMD_UPDATE: + ExecASUpdateTriggers(node->ps.state, node->ps.state->es_result_relation_info); + break; + case CMD_DELETE: + ExecASDeleteTriggers(node->ps.state, node->ps.state->es_result_relation_info); + break; + case CMD_INSERT: + ExecASInsertTriggers(node->ps.state, node->ps.state->es_result_relation_info); + break; + default: + elog(ERROR, "unknown operation"); + break; + } + } + + 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; + + /* Do we need to fire BEFORE STATEMENT triggers? */ + if (node->fireBSTriggers) + { + fireBSTriggers(node); + node->fireBSTriggers = false; + } + + 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 + { + fireASTriggers(node); + 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; + dmlstate->fireBSTriggers = true; + + 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"); + } + + resultRelInfo->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;ids_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 ---- *************** *** 1407,1412 **** _copyXmlExpr(XmlExpr *from) --- 1406,1426 ---- 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(returningLists); + + return newnode; + } + /* * _copyNullIfExpr (same as OpExpr) *************** *** 4131,4136 **** copyObject(void *from) --- 4145,4153 ---- 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,3716 ---- return node; } + Dml * + make_dml(List *subplans, List *returningLists, CmdType operation) + { + Dml *node = makeNode(Dml); + Plan *plan = &node->plan; + double total_size; + ListCell *subnode; + + /* + * 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->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,493 ---- 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, + 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 *************** *** 625,633 **** 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. * *************** *** 738,748 **** 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,752 ---- */ parse->rtable = rtable; ! return (Plan *) make_dml(subplans, ! root->returningLists, ! 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,398 ---- 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; + + 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,997 ---- } ResultState; /* ---------------- + * DmlState information + * ---------------- + */ + typedef struct DmlState + { + PlanState ps; /* its first field is NodeTag */ + PlanState **dmlplans; + int ds_nplans; + int ds_whichplan; + bool fireBSTriggers; + + 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, *************** *** 191,196 **** typedef enum NodeTag --- 192,198 ---- 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,188 ---- Node *resconstantqual; } Result; + typedef struct Dml + { + Plan plan; + + CmdType operation; + List *plans; + 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,75 ---- 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, CmdType operation); extern SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree, List *distinctList, AttrNumber flagColIdx, int firstFlag, long numGroups, double outputRows);