*** a/src/backend/commands/explain.c --- b/src/backend/commands/explain.c *************** *** 705,710 **** ExplainNode(Plan *plan, PlanState *planstate, --- 705,727 ---- case T_Hash: pname = sname = "Hash"; break; + case T_Dml: + switch( ((Dml *) plan)->operation) + { + case CMD_INSERT: + pname = "INSERT"; + break; + case CMD_UPDATE: + pname = "UPDATE"; + break; + case CMD_DELETE: + pname = "DELETE"; + break; + default: + pname = "???"; + break; + } + break; default: pname = sname = "???"; break; *************** *** 1064,1069 **** ExplainNode(Plan *plan, PlanState *planstate, --- 1081,1091 ---- ((AppendState *) planstate)->appendplans, outer_plan, es); break; + case T_Dml: + ExplainMemberNodes(((Dml *) plan)->plans, + ((DmlState *) planstate)->dmlplans, + outer_plan, es); + break; case T_BitmapAnd: ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans, ((BitmapAndState *) planstate)->bitmapplans, *** a/src/backend/executor/Makefile --- b/src/backend/executor/Makefile *************** *** 15,21 **** include $(top_builddir)/src/Makefile.global OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \ execProcnode.o execQual.o execScan.o execTuples.o \ execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \ ! nodeBitmapAnd.o nodeBitmapOr.o \ nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \ nodeHashjoin.o nodeIndexscan.o nodeMaterial.o nodeMergejoin.o \ nodeNestloop.o nodeFunctionscan.o nodeRecursiveunion.o nodeResult.o \ --- 15,21 ---- OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \ execProcnode.o execQual.o execScan.o execTuples.o \ execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \ ! nodeBitmapAnd.o nodeBitmapOr.o nodeDml.o \ nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \ nodeHashjoin.o nodeIndexscan.o nodeMaterial.o nodeMergejoin.o \ nodeNestloop.o nodeFunctionscan.o nodeRecursiveunion.o nodeResult.o \ *** a/src/backend/executor/execMain.c --- b/src/backend/executor/execMain.c *************** *** 77,83 **** typedef struct evalPlanQual /* decls for local routines only used within this module */ static void InitPlan(QueryDesc *queryDesc, int eflags); - static void ExecCheckPlanOutput(Relation resultRel, List *targetList); static void ExecEndPlan(PlanState *planstate, EState *estate); static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, --- 77,82 ---- *************** *** 86,104 **** static void ExecutePlan(EState *estate, PlanState *planstate, DestReceiver *dest); static void ExecSelect(TupleTableSlot *slot, DestReceiver *dest, EState *estate); - static void ExecInsert(TupleTableSlot *slot, ItemPointer tupleid, - TupleTableSlot *planSlot, - DestReceiver *dest, EState *estate); - static void ExecDelete(ItemPointer tupleid, - TupleTableSlot *planSlot, - DestReceiver *dest, EState *estate); - static void ExecUpdate(TupleTableSlot *slot, ItemPointer tupleid, - TupleTableSlot *planSlot, - DestReceiver *dest, EState *estate); - static void ExecProcessReturning(ProjectionInfo *projectReturning, - TupleTableSlot *tupleSlot, - TupleTableSlot *planSlot, - DestReceiver *dest); static TupleTableSlot *EvalPlanQualNext(EState *estate); static void EndEvalPlanQual(EState *estate); static void ExecCheckRTPerms(List *rangeTable); --- 85,90 ---- *************** *** 695,700 **** InitPlan(QueryDesc *queryDesc, int eflags) --- 681,687 ---- estate->es_instrument); resultRelInfo++; } + estate->es_result_relations = resultRelInfos; estate->es_num_result_relations = numResultRelations; /* Initialize to first or only result rel */ *************** *** 756,765 **** InitPlan(QueryDesc *queryDesc, int eflags) /* * Initialize the executor "tuple" table. We need slots for all the plan ! * nodes, plus possibly output slots for the junkfilter(s). At this point ! * we aren't sure if we need junkfilters, so just add slots for them ! * unconditionally. Also, if it's not a SELECT, set up a slot for use for ! * trigger output tuples. Also, one for RETURNING-list evaluation. */ { int nSlots; --- 743,749 ---- /* * Initialize the executor "tuple" table. We need slots for all the plan ! * nodes, plus possibly a slot for use for trigger output tuples. */ { int nSlots; *************** *** 773,787 **** InitPlan(QueryDesc *queryDesc, int eflags) nSlots += ExecCountSlotsNode(subplan); } ! /* Add slots for junkfilter(s) */ ! if (plannedstmt->resultRelations != NIL) ! nSlots += list_length(plannedstmt->resultRelations); ! else ! nSlots += 1; ! if (operation != CMD_SELECT) ! nSlots++; /* for es_trig_tuple_slot */ ! if (plannedstmt->returningLists) ! nSlots++; /* for RETURNING projection */ estate->es_tupleTable = ExecCreateTupleTable(nSlots); --- 757,769 ---- nSlots += ExecCountSlotsNode(subplan); } ! ! /* ! * In SELECT, we might need one for a junkfilter. In DML, ! * the DML node takes care of reserving slots for ! * junkfilters, but we need one for es_trig_tuple_slot. ! */ ! nSlots++; estate->es_tupleTable = ExecCreateTupleTable(nSlots); *************** *** 842,937 **** InitPlan(QueryDesc *queryDesc, int eflags) tupType = ExecGetResultType(planstate); /* ! * Initialize the junk filter if needed. SELECT and INSERT queries need a ! * filter if there are any junk attrs in the tlist. UPDATE and DELETE ! * always need a filter, since there's always a junk 'ctid' attribute ! * present --- no need to look first. ! * ! * This section of code is also a convenient place to verify that the ! * output of an INSERT or UPDATE matches the target table(s). */ { bool junk_filter_needed = false; ListCell *tlist; ! switch (operation) { ! case CMD_SELECT: ! case CMD_INSERT: ! foreach(tlist, plan->targetlist) ! { ! TargetEntry *tle = (TargetEntry *) lfirst(tlist); ! if (tle->resjunk) ! { ! junk_filter_needed = true; ! break; ! } ! } ! break; ! case CMD_UPDATE: ! case CMD_DELETE: junk_filter_needed = true; break; ! default: ! break; } if (junk_filter_needed) { - /* - * If there are multiple result relations, each one needs its own - * junk filter. Note this is only possible for UPDATE/DELETE, so - * we can't be fooled by some needing a filter and some not. - */ if (list_length(plannedstmt->resultRelations) > 1) { - PlanState **appendplans; - int as_nplans; - ResultRelInfo *resultRelInfo; - - /* Top plan had better be an Append here. */ - Assert(IsA(plan, Append)); - Assert(((Append *) plan)->isTarget); - Assert(IsA(planstate, AppendState)); - appendplans = ((AppendState *) planstate)->appendplans; - as_nplans = ((AppendState *) planstate)->as_nplans; - Assert(as_nplans == estate->es_num_result_relations); - resultRelInfo = estate->es_result_relations; - for (i = 0; i < as_nplans; i++) - { - PlanState *subplan = appendplans[i]; - JunkFilter *j; - - if (operation == CMD_UPDATE) - ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, - subplan->plan->targetlist); - - j = ExecInitJunkFilter(subplan->plan->targetlist, - resultRelInfo->ri_RelationDesc->rd_att->tdhasoid, - ExecAllocTableSlot(estate->es_tupleTable)); - - /* - * Since it must be UPDATE/DELETE, there had better be a - * "ctid" junk attribute in the tlist ... but ctid could - * be at a different resno for each result relation. We - * look up the ctid resnos now and save them in the - * junkfilters. - */ - j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); - if (!AttributeNumberIsValid(j->jf_junkAttNo)) - elog(ERROR, "could not find junk ctid column"); - resultRelInfo->ri_junkFilter = j; - resultRelInfo++; - } - - /* - * Set active junkfilter too; at this point ExecInitAppend has - * already selected an active result relation... - */ - estate->es_junkFilter = - estate->es_result_relation_info->ri_junkFilter; - /* * We currently can't support rowmarks in this case, because * the associated junk CTIDs might have different resnos in --- 824,852 ---- tupType = ExecGetResultType(planstate); /* ! * Initialize the junk filter if needed. SELECT queries need a ! * filter if there are any junk attrs in the tlist. */ + if (operation == CMD_SELECT) { bool junk_filter_needed = false; ListCell *tlist; ! foreach(tlist, plan->targetlist) { ! TargetEntry *tle = (TargetEntry *) lfirst(tlist); ! if (tle->resjunk) ! { junk_filter_needed = true; break; ! } } if (junk_filter_needed) { if (list_length(plannedstmt->resultRelations) > 1) { /* * We currently can't support rowmarks in this case, because * the associated junk CTIDs might have different resnos in *************** *** 944,956 **** InitPlan(QueryDesc *queryDesc, int eflags) } else { - /* Normal case with just one JunkFilter */ JunkFilter *j; - if (operation == CMD_INSERT || operation == CMD_UPDATE) - ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc, - planstate->plan->targetlist); - j = ExecInitJunkFilter(planstate->plan->targetlist, tupType->tdhasoid, ExecAllocTableSlot(estate->es_tupleTable)); --- 859,866 ---- *************** *** 958,975 **** InitPlan(QueryDesc *queryDesc, int eflags) if (estate->es_result_relation_info) estate->es_result_relation_info->ri_junkFilter = j; ! if (operation == CMD_SELECT) ! { ! /* For SELECT, want to return the cleaned tuple type */ ! tupType = j->jf_cleanTupType; ! } ! else if (operation == CMD_UPDATE || operation == CMD_DELETE) ! { ! /* For UPDATE/DELETE, find the ctid junk attr now */ ! j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); ! if (!AttributeNumberIsValid(j->jf_junkAttNo)) ! elog(ERROR, "could not find junk ctid column"); ! } /* For SELECT FOR UPDATE/SHARE, find the junk attrs now */ foreach(l, estate->es_rowMarks) --- 868,875 ---- if (estate->es_result_relation_info) estate->es_result_relation_info->ri_junkFilter = j; ! /* For SELECT, want to return the cleaned tuple type */ ! tupType = j->jf_cleanTupType; /* For SELECT FOR UPDATE/SHARE, find the junk attrs now */ foreach(l, estate->es_rowMarks) *************** *** 999,1055 **** InitPlan(QueryDesc *queryDesc, int eflags) } else { - if (operation == CMD_INSERT) - ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc, - planstate->plan->targetlist); - estate->es_junkFilter = NULL; if (estate->es_rowMarks) elog(ERROR, "SELECT FOR UPDATE/SHARE, but no junk columns"); } } - /* - * Initialize RETURNING projections if needed. - */ - if (plannedstmt->returningLists) - { - TupleTableSlot *slot; - ExprContext *econtext; - ResultRelInfo *resultRelInfo; - - /* - * We set QueryDesc.tupDesc to be the RETURNING rowtype in this case. - * We assume all the sublists will generate the same output tupdesc. - */ - tupType = ExecTypeFromTL((List *) linitial(plannedstmt->returningLists), - false); - - /* Set up a slot for the output of the RETURNING projection(s) */ - slot = ExecAllocTableSlot(estate->es_tupleTable); - ExecSetSlotDescriptor(slot, tupType); - /* Need an econtext too */ - econtext = CreateExprContext(estate); - - /* - * Build a projection for each result rel. Note that any SubPlans in - * the RETURNING lists get attached to the topmost plan node. - */ - Assert(list_length(plannedstmt->returningLists) == estate->es_num_result_relations); - resultRelInfo = estate->es_result_relations; - foreach(l, plannedstmt->returningLists) - { - List *rlist = (List *) lfirst(l); - List *rliststate; - - rliststate = (List *) ExecInitExpr((Expr *) rlist, planstate); - resultRelInfo->ri_projectReturning = - ExecBuildProjectionInfo(rliststate, econtext, slot, - resultRelInfo->ri_RelationDesc->rd_att); - resultRelInfo++; - } - } - queryDesc->tupDesc = tupType; queryDesc->planstate = planstate; --- 899,910 ---- *************** *** 1151,1225 **** InitResultRelInfo(ResultRelInfo *resultRelInfo, } /* - * Verify that the tuples to be produced by INSERT or UPDATE match the - * target relation's rowtype - * - * We do this to guard against stale plans. If plan invalidation is - * functioning properly then we should never get a failure here, but better - * safe than sorry. Note that this is called after we have obtained lock - * on the target rel, so the rowtype can't change underneath us. - * - * The plan output is represented by its targetlist, because that makes - * handling the dropped-column case easier. - */ - static void - ExecCheckPlanOutput(Relation resultRel, List *targetList) - { - TupleDesc resultDesc = RelationGetDescr(resultRel); - int attno = 0; - ListCell *lc; - - foreach(lc, targetList) - { - TargetEntry *tle = (TargetEntry *) lfirst(lc); - Form_pg_attribute attr; - - if (tle->resjunk) - continue; /* ignore junk tlist items */ - - if (attno >= resultDesc->natts) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("table row type and query-specified row type do not match"), - errdetail("Query has too many columns."))); - attr = resultDesc->attrs[attno++]; - - if (!attr->attisdropped) - { - /* Normal case: demand type match */ - if (exprType((Node *) tle->expr) != attr->atttypid) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("table row type and query-specified row type do not match"), - errdetail("Table has type %s at ordinal position %d, but query expects %s.", - format_type_be(attr->atttypid), - attno, - format_type_be(exprType((Node *) tle->expr))))); - } - else - { - /* - * For a dropped column, we can't check atttypid (it's likely 0). - * In any case the planner has most likely inserted an INT4 null. - * What we insist on is just *some* NULL constant. - */ - if (!IsA(tle->expr, Const) || - !((Const *) tle->expr)->constisnull) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("table row type and query-specified row type do not match"), - errdetail("Query provides a value for a dropped column at ordinal position %d.", - attno))); - } - } - if (attno != resultDesc->natts) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("table row type and query-specified row type do not match"), - errdetail("Query has too few columns."))); - } - - /* * ExecGetTriggerResultRel * * Get a ResultRelInfo for a trigger target relation. Most of the time, --- 1006,1011 ---- *************** *** 1449,1456 **** ExecutePlan(EState *estate, JunkFilter *junkfilter; TupleTableSlot *planSlot; TupleTableSlot *slot; - ItemPointer tupleid = NULL; - ItemPointerData tuple_ctid; long current_tuple_count; /* --- 1235,1240 ---- *************** *** 1521,1527 **** lnext: ; * * But first, extract all the junk information we need. */ ! if ((junkfilter = estate->es_junkFilter) != NULL) { /* * Process any FOR UPDATE or FOR SHARE locking requested. --- 1305,1311 ---- * * But first, extract all the junk information we need. */ ! if (operation == CMD_SELECT && (junkfilter = estate->es_junkFilter) != NULL) { /* * Process any FOR UPDATE or FOR SHARE locking requested. *************** *** 1630,1661 **** lnext: ; } } ! /* ! * extract the 'ctid' junk attribute. ! */ ! if (operation == CMD_UPDATE || operation == CMD_DELETE) ! { ! Datum datum; ! bool isNull; ! ! datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, ! &isNull); ! /* shouldn't ever get a null result... */ ! if (isNull) ! elog(ERROR, "ctid is NULL"); ! ! tupleid = (ItemPointer) DatumGetPointer(datum); ! tuple_ctid = *tupleid; /* make sure we don't free the ctid!! */ ! tupleid = &tuple_ctid; ! } ! ! /* ! * Create a new "clean" tuple with all junk attributes removed. We ! * don't need to do this for DELETE, however (there will in fact ! * be no non-junk attributes in a DELETE!) ! */ ! if (operation != CMD_DELETE) ! slot = ExecFilterJunk(junkfilter, slot); } /* --- 1414,1420 ---- } } ! slot = ExecFilterJunk(junkfilter, slot); } /* *************** *** 1670,1684 **** lnext: ; break; case CMD_INSERT: - ExecInsert(slot, tupleid, planSlot, dest, estate); - break; - case CMD_DELETE: - ExecDelete(tupleid, planSlot, dest, estate); - break; - case CMD_UPDATE: ! ExecUpdate(slot, tupleid, planSlot, dest, estate); break; default: --- 1429,1438 ---- break; case CMD_INSERT: case CMD_DELETE: case CMD_UPDATE: ! if (estate->es_plannedstmt->returningLists) ! (*dest->receiveSlot) (slot, dest); break; default: *************** *** 1734,2153 **** ExecSelect(TupleTableSlot *slot, (estate->es_processed)++; } - /* ---------------------------------------------------------------- - * ExecInsert - * - * INSERTs are trickier.. we have to insert the tuple into - * the base relation and insert appropriate tuples into the - * index relations. - * ---------------------------------------------------------------- - */ - static void - ExecInsert(TupleTableSlot *slot, - ItemPointer tupleid, - TupleTableSlot *planSlot, - DestReceiver *dest, - EState *estate) - { - HeapTuple tuple; - ResultRelInfo *resultRelInfo; - Relation resultRelationDesc; - Oid newId; - List *recheckIndexes = NIL; - - /* - * get the heap tuple out of the tuple table slot, making sure we have a - * writable copy - */ - tuple = ExecMaterializeSlot(slot); - - /* - * get information on the (current) result relation - */ - resultRelInfo = estate->es_result_relation_info; - resultRelationDesc = resultRelInfo->ri_RelationDesc; - - /* - * If the result relation has OIDs, force the tuple's OID to zero so that - * heap_insert will assign a fresh OID. Usually the OID already will be - * zero at this point, but there are corner cases where the plan tree can - * return a tuple extracted literally from some table with the same - * rowtype. - * - * XXX if we ever wanted to allow users to assign their own OIDs to new - * rows, this'd be the place to do it. For the moment, we make a point of - * doing this before calling triggers, so that a user-supplied trigger - * could hack the OID if desired. - */ - if (resultRelationDesc->rd_rel->relhasoids) - HeapTupleSetOid(tuple, InvalidOid); - - /* BEFORE ROW INSERT Triggers */ - if (resultRelInfo->ri_TrigDesc && - resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0) - { - HeapTuple newtuple; - - newtuple = ExecBRInsertTriggers(estate, resultRelInfo, tuple); - - if (newtuple == NULL) /* "do nothing" */ - return; - - if (newtuple != tuple) /* modified by Trigger(s) */ - { - /* - * Put the modified tuple into a slot for convenience of routines - * below. We assume the tuple was allocated in per-tuple memory - * context, and therefore will go away by itself. The tuple table - * slot should not try to clear it. - */ - TupleTableSlot *newslot = estate->es_trig_tuple_slot; - - if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor) - ExecSetSlotDescriptor(newslot, slot->tts_tupleDescriptor); - ExecStoreTuple(newtuple, newslot, InvalidBuffer, false); - slot = newslot; - tuple = newtuple; - } - } - - /* - * Check the constraints of the tuple - */ - if (resultRelationDesc->rd_att->constr) - ExecConstraints(resultRelInfo, slot, estate); - - /* - * insert the tuple - * - * Note: heap_insert returns the tid (location) of the new tuple in the - * t_self field. - */ - newId = heap_insert(resultRelationDesc, tuple, - estate->es_output_cid, 0, NULL); - - IncrAppended(); - (estate->es_processed)++; - estate->es_lastoid = newId; - setLastTid(&(tuple->t_self)); - - /* - * insert index entries for tuple - */ - if (resultRelInfo->ri_NumIndices > 0) - recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), - estate, false); - - /* AFTER ROW INSERT Triggers */ - ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes); - - /* Process RETURNING if present */ - if (resultRelInfo->ri_projectReturning) - ExecProcessReturning(resultRelInfo->ri_projectReturning, - slot, planSlot, dest); - } - - /* ---------------------------------------------------------------- - * ExecDelete - * - * DELETE is like UPDATE, except that we delete the tuple and no - * index modifications are needed - * ---------------------------------------------------------------- - */ - static void - ExecDelete(ItemPointer tupleid, - TupleTableSlot *planSlot, - DestReceiver *dest, - EState *estate) - { - ResultRelInfo *resultRelInfo; - Relation resultRelationDesc; - HTSU_Result result; - ItemPointerData update_ctid; - TransactionId update_xmax; - - /* - * get information on the (current) result relation - */ - resultRelInfo = estate->es_result_relation_info; - resultRelationDesc = resultRelInfo->ri_RelationDesc; - - /* BEFORE ROW DELETE Triggers */ - if (resultRelInfo->ri_TrigDesc && - resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0) - { - bool dodelete; - - dodelete = ExecBRDeleteTriggers(estate, resultRelInfo, tupleid); - - if (!dodelete) /* "do nothing" */ - return; - } - - /* - * delete the tuple - * - * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that - * the row to be deleted is visible to that snapshot, and throw a can't- - * serialize error if not. This is a special-case behavior needed for - * referential integrity updates in serializable transactions. - */ - ldelete:; - result = heap_delete(resultRelationDesc, tupleid, - &update_ctid, &update_xmax, - estate->es_output_cid, - estate->es_crosscheck_snapshot, - true /* wait for commit */ ); - switch (result) - { - case HeapTupleSelfUpdated: - /* already deleted by self; nothing to do */ - return; - - case HeapTupleMayBeUpdated: - break; - - case HeapTupleUpdated: - if (IsXactIsoLevelSerializable) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("could not serialize access due to concurrent update"))); - else if (!ItemPointerEquals(tupleid, &update_ctid)) - { - TupleTableSlot *epqslot; - - epqslot = EvalPlanQual(estate, - resultRelInfo->ri_RangeTableIndex, - &update_ctid, - update_xmax); - if (!TupIsNull(epqslot)) - { - *tupleid = update_ctid; - goto ldelete; - } - } - /* tuple already deleted; nothing to do */ - return; - - default: - elog(ERROR, "unrecognized heap_delete status: %u", result); - return; - } - - IncrDeleted(); - (estate->es_processed)++; - - /* - * Note: Normally one would think that we have to delete index tuples - * associated with the heap tuple now... - * - * ... but in POSTGRES, we have no need to do this because VACUUM will - * take care of it later. We can't delete index tuples immediately - * anyway, since the tuple is still visible to other transactions. - */ - - /* AFTER ROW DELETE Triggers */ - ExecARDeleteTriggers(estate, resultRelInfo, tupleid); - - /* Process RETURNING if present */ - if (resultRelInfo->ri_projectReturning) - { - /* - * We have to put the target tuple into a slot, which means first we - * gotta fetch it. We can use the trigger tuple slot. - */ - TupleTableSlot *slot = estate->es_trig_tuple_slot; - HeapTupleData deltuple; - Buffer delbuffer; - - deltuple.t_self = *tupleid; - if (!heap_fetch(resultRelationDesc, SnapshotAny, - &deltuple, &delbuffer, false, NULL)) - elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING"); - - if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc)) - ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc)); - ExecStoreTuple(&deltuple, slot, InvalidBuffer, false); - - ExecProcessReturning(resultRelInfo->ri_projectReturning, - slot, planSlot, dest); - - ExecClearTuple(slot); - ReleaseBuffer(delbuffer); - } - } - - /* ---------------------------------------------------------------- - * ExecUpdate - * - * note: we can't run UPDATE queries with transactions - * off because UPDATEs are actually INSERTs and our - * scan will mistakenly loop forever, updating the tuple - * it just inserted.. This should be fixed but until it - * is, we don't want to get stuck in an infinite loop - * which corrupts your database.. - * ---------------------------------------------------------------- - */ - static void - ExecUpdate(TupleTableSlot *slot, - ItemPointer tupleid, - TupleTableSlot *planSlot, - DestReceiver *dest, - EState *estate) - { - HeapTuple tuple; - ResultRelInfo *resultRelInfo; - Relation resultRelationDesc; - HTSU_Result result; - ItemPointerData update_ctid; - TransactionId update_xmax; - List *recheckIndexes = NIL; - - /* - * abort the operation if not running transactions - */ - if (IsBootstrapProcessingMode()) - elog(ERROR, "cannot UPDATE during bootstrap"); - - /* - * get the heap tuple out of the tuple table slot, making sure we have a - * writable copy - */ - tuple = ExecMaterializeSlot(slot); - - /* - * get information on the (current) result relation - */ - resultRelInfo = estate->es_result_relation_info; - resultRelationDesc = resultRelInfo->ri_RelationDesc; - - /* BEFORE ROW UPDATE Triggers */ - if (resultRelInfo->ri_TrigDesc && - resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0) - { - HeapTuple newtuple; - - newtuple = ExecBRUpdateTriggers(estate, resultRelInfo, - tupleid, tuple); - - if (newtuple == NULL) /* "do nothing" */ - return; - - if (newtuple != tuple) /* modified by Trigger(s) */ - { - /* - * Put the modified tuple into a slot for convenience of routines - * below. We assume the tuple was allocated in per-tuple memory - * context, and therefore will go away by itself. The tuple table - * slot should not try to clear it. - */ - TupleTableSlot *newslot = estate->es_trig_tuple_slot; - - if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor) - ExecSetSlotDescriptor(newslot, slot->tts_tupleDescriptor); - ExecStoreTuple(newtuple, newslot, InvalidBuffer, false); - slot = newslot; - tuple = newtuple; - } - } - - /* - * Check the constraints of the tuple - * - * If we generate a new candidate tuple after EvalPlanQual testing, we - * must loop back here and recheck constraints. (We don't need to redo - * triggers, however. If there are any BEFORE triggers then trigger.c - * will have done heap_lock_tuple to lock the correct tuple, so there's no - * need to do them again.) - */ - lreplace:; - if (resultRelationDesc->rd_att->constr) - ExecConstraints(resultRelInfo, slot, estate); - - /* - * replace the heap tuple - * - * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that - * the row to be updated is visible to that snapshot, and throw a can't- - * serialize error if not. This is a special-case behavior needed for - * referential integrity updates in serializable transactions. - */ - result = heap_update(resultRelationDesc, tupleid, tuple, - &update_ctid, &update_xmax, - estate->es_output_cid, - estate->es_crosscheck_snapshot, - true /* wait for commit */ ); - switch (result) - { - case HeapTupleSelfUpdated: - /* already deleted by self; nothing to do */ - return; - - case HeapTupleMayBeUpdated: - break; - - case HeapTupleUpdated: - if (IsXactIsoLevelSerializable) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("could not serialize access due to concurrent update"))); - else if (!ItemPointerEquals(tupleid, &update_ctid)) - { - TupleTableSlot *epqslot; - - epqslot = EvalPlanQual(estate, - resultRelInfo->ri_RangeTableIndex, - &update_ctid, - update_xmax); - if (!TupIsNull(epqslot)) - { - *tupleid = update_ctid; - slot = ExecFilterJunk(estate->es_junkFilter, epqslot); - tuple = ExecMaterializeSlot(slot); - goto lreplace; - } - } - /* tuple already deleted; nothing to do */ - return; - - default: - elog(ERROR, "unrecognized heap_update status: %u", result); - return; - } - - IncrReplaced(); - (estate->es_processed)++; - - /* - * Note: instead of having to update the old index tuples associated with - * the heap tuple, all we do is form and insert new index tuples. This is - * because UPDATEs are actually DELETEs and INSERTs, and index tuple - * deletion is done later by VACUUM (see notes in ExecDelete). All we do - * here is insert new index tuples. -cim 9/27/89 - */ - - /* - * insert index entries for tuple - * - * Note: heap_update returns the tid (location) of the new tuple in the - * t_self field. - * - * If it's a HOT update, we mustn't insert new index entries. - */ - if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple)) - recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), - estate, false); - - /* AFTER ROW UPDATE Triggers */ - ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple, - recheckIndexes); - - /* Process RETURNING if present */ - if (resultRelInfo->ri_projectReturning) - ExecProcessReturning(resultRelInfo->ri_projectReturning, - slot, planSlot, dest); - } - /* * ExecRelCheck --- check that tuple meets constraints for result relation */ --- 1488,1493 ---- *************** *** 2248,2289 **** ExecConstraints(ResultRelInfo *resultRelInfo, } /* - * ExecProcessReturning --- evaluate a RETURNING list and send to dest - * - * projectReturning: RETURNING projection info for current result rel - * tupleSlot: slot holding tuple actually inserted/updated/deleted - * planSlot: slot holding tuple returned by top plan node - * dest: where to send the output - */ - static void - ExecProcessReturning(ProjectionInfo *projectReturning, - TupleTableSlot *tupleSlot, - TupleTableSlot *planSlot, - DestReceiver *dest) - { - ExprContext *econtext = projectReturning->pi_exprContext; - TupleTableSlot *retSlot; - - /* - * Reset per-tuple memory context to free any expression evaluation - * storage allocated in the previous cycle. - */ - ResetExprContext(econtext); - - /* Make tuple and any needed join variables available to ExecProject */ - econtext->ecxt_scantuple = tupleSlot; - econtext->ecxt_outertuple = planSlot; - - /* Compute the RETURNING expressions */ - retSlot = ExecProject(projectReturning, NULL); - - /* Send to dest */ - (*dest->receiveSlot) (retSlot, dest); - - ExecClearTuple(retSlot); - } - - /* * Check a modified tuple to see if we want to process its updated version * under READ COMMITTED rules. * --- 1588,1593 ---- *** a/src/backend/executor/execProcnode.c --- b/src/backend/executor/execProcnode.c *************** *** 91,96 **** --- 91,97 ---- #include "executor/nodeHash.h" #include "executor/nodeHashjoin.h" #include "executor/nodeIndexscan.h" + #include "executor/nodeDml.h" #include "executor/nodeLimit.h" #include "executor/nodeMaterial.h" #include "executor/nodeMergejoin.h" *************** *** 286,291 **** ExecInitNode(Plan *node, EState *estate, int eflags) --- 287,297 ---- estate, eflags); break; + case T_Dml: + result = (PlanState *) ExecInitDml((Dml *) node, + estate, eflags); + break; + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); result = NULL; /* keep compiler quiet */ *************** *** 451,456 **** ExecProcNode(PlanState *node) --- 457,466 ---- result = ExecLimit((LimitState *) node); break; + case T_DmlState: + result = ExecDml((DmlState *) node); + break; + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); result = NULL; *************** *** 627,632 **** ExecCountSlotsNode(Plan *node) --- 637,645 ---- case T_Limit: return ExecCountSlotsLimit((Limit *) node); + case T_Dml: + return ExecCountSlotsDml((Dml *) node); + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); break; *************** *** 783,788 **** ExecEndNode(PlanState *node) --- 796,805 ---- ExecEndLimit((LimitState *) node); break; + case T_DmlState: + ExecEndDml((DmlState *) node); + break; + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); break; *** a/src/backend/executor/nodeAppend.c --- b/src/backend/executor/nodeAppend.c *************** *** 103,123 **** exec_append_initialize_next(AppendState *appendstate) } else { - /* - * initialize the scan - * - * If we are controlling the target relation, select the proper active - * ResultRelInfo and junk filter for this target. - */ - if (((Append *) appendstate->ps.plan)->isTarget) - { - Assert(whichplan < estate->es_num_result_relations); - estate->es_result_relation_info = - estate->es_result_relations + whichplan; - estate->es_junkFilter = - estate->es_result_relation_info->ri_junkFilter; - } - return TRUE; } } --- 103,108 ---- *************** *** 164,189 **** ExecInitAppend(Append *node, EState *estate, int eflags) appendstate->appendplans = appendplanstates; appendstate->as_nplans = nplans; ! /* ! * Do we want to scan just one subplan? (Special case for EvalPlanQual) ! * XXX pretty dirty way of determining that this case applies ... ! */ ! if (node->isTarget && estate->es_evTuple != NULL) ! { ! int tplan; ! ! tplan = estate->es_result_relation_info - estate->es_result_relations; ! Assert(tplan >= 0 && tplan < nplans); ! ! appendstate->as_firstplan = tplan; ! appendstate->as_lastplan = tplan; ! } ! else ! { ! /* normal case, scan all subplans */ ! appendstate->as_firstplan = 0; ! appendstate->as_lastplan = nplans - 1; ! } /* * Miscellaneous initialization --- 149,157 ---- appendstate->appendplans = appendplanstates; appendstate->as_nplans = nplans; ! ! appendstate->as_firstplan = 0; ! appendstate->as_lastplan = nplans - 1; /* * Miscellaneous initialization *** /dev/null --- b/src/backend/executor/nodeDml.c *************** *** 0 **** --- 1,829 ---- + #include "postgres.h" + + #include "access/xact.h" + #include "parser/parsetree.h" + #include "executor/executor.h" + #include "executor/execdebug.h" + #include "executor/nodeDml.h" + #include "commands/trigger.h" + #include "nodes/nodeFuncs.h" + #include "utils/memutils.h" + #include "utils/builtins.h" + #include "utils/tqual.h" + #include "storage/bufmgr.h" + #include "miscadmin.h" + + /* + * Verify that the tuples to be produced by INSERT or UPDATE match the + * target relation's rowtype + * + * We do this to guard against stale plans. If plan invalidation is + * functioning properly then we should never get a failure here, but better + * safe than sorry. Note that this is called after we have obtained lock + * on the target rel, so the rowtype can't change underneath us. + * + * The plan output is represented by its targetlist, because that makes + * handling the dropped-column case easier. + */ + static void + ExecCheckPlanOutput(Relation resultRel, List *targetList) + { + TupleDesc resultDesc = RelationGetDescr(resultRel); + int attno = 0; + ListCell *lc; + + foreach(lc, targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + Form_pg_attribute attr; + + if (tle->resjunk) + continue; /* ignore junk tlist items */ + + if (attno >= resultDesc->natts) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail("Query has too many columns."))); + attr = resultDesc->attrs[attno++]; + + if (!attr->attisdropped) + { + /* Normal case: demand type match */ + if (exprType((Node *) tle->expr) != attr->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail("Table has type %s at ordinal position %d, but query expects %s.", + format_type_be(attr->atttypid), + attno, + format_type_be(exprType((Node *) tle->expr))))); + } + else + { + /* + * For a dropped column, we can't check atttypid (it's likely 0). + * In any case the planner has most likely inserted an INT4 null. + * What we insist on is just *some* NULL constant. + */ + if (!IsA(tle->expr, Const) || + !((Const *) tle->expr)->constisnull) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail("Query provides a value for a dropped column at ordinal position %d.", + attno))); + } + } + if (attno != resultDesc->natts) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail("Query has too few columns."))); + } + + static TupleTableSlot* + ExecProcessReturning(ProjectionInfo *projectReturning, + TupleTableSlot *tupleSlot, + TupleTableSlot *planSlot) + { + ExprContext *econtext = projectReturning->pi_exprContext; + TupleTableSlot *retSlot; + + /* + * Reset per-tuple memory context to free any expression evaluation + * storage allocated in the previous cycle. + */ + ResetExprContext(econtext); + + /* Make tuple and any needed join variables available to ExecProject */ + econtext->ecxt_scantuple = tupleSlot; + econtext->ecxt_outertuple = planSlot; + + /* Compute the RETURNING expressions */ + retSlot = ExecProject(projectReturning, NULL); + + return retSlot; + } + + static TupleTableSlot * + ExecInsert(TupleTableSlot *slot, + ItemPointer tupleid, + TupleTableSlot *planSlot, + EState *estate) + { + HeapTuple tuple; + ResultRelInfo *resultRelInfo; + Relation resultRelationDesc; + Oid newId; + List *recheckIndexes = NIL; + + /* + * get the heap tuple out of the tuple table slot, making sure we have a + * writable copy + */ + tuple = ExecMaterializeSlot(slot); + + /* + * get information on the (current) result relation + */ + resultRelInfo = estate->es_result_relations; + resultRelationDesc = resultRelInfo->ri_RelationDesc; + + /* + * If the result relation has OIDs, force the tuple's OID to zero so that + * heap_insert will assign a fresh OID. Usually the OID already will be + * zero at this point, but there are corner cases where the plan tree can + * return a tuple extracted literally from some table with the same + * rowtype. + * + * XXX if we ever wanted to allow users to assign their own OIDs to new + * rows, this'd be the place to do it. For the moment, we make a point of + * doing this before calling triggers, so that a user-supplied trigger + * could hack the OID if desired. + */ + if (resultRelationDesc->rd_rel->relhasoids) + HeapTupleSetOid(tuple, InvalidOid); + + /* BEFORE ROW INSERT Triggers */ + if (resultRelInfo->ri_TrigDesc && + resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0) + { + HeapTuple newtuple; + + newtuple = ExecBRInsertTriggers(estate, resultRelInfo, tuple); + + if (newtuple == NULL) /* "do nothing" */ + return NULL; + + if (newtuple != tuple) /* modified by Trigger(s) */ + { + /* + * Put the modified tuple into a slot for convenience of routines + * below. We assume the tuple was allocated in per-tuple memory + * context, and therefore will go away by itself. The tuple table + * slot should not try to clear it. + */ + TupleTableSlot *newslot = estate->es_trig_tuple_slot; + + if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor) + ExecSetSlotDescriptor(newslot, slot->tts_tupleDescriptor); + ExecStoreTuple(newtuple, newslot, InvalidBuffer, false); + slot = newslot; + tuple = newtuple; + } + } + + /* + * Check the constraints of the tuple + */ + if (resultRelationDesc->rd_att->constr) + ExecConstraints(resultRelInfo, slot, estate); + + /* + * insert the tuple + * + * Note: heap_insert returns the tid (location) of the new tuple in the + * t_self field. + */ + newId = heap_insert(resultRelationDesc, tuple, + estate->es_output_cid, 0, NULL); + + IncrAppended(); + (estate->es_processed)++; + estate->es_lastoid = newId; + setLastTid(&(tuple->t_self)); + + /* + * insert index entries for tuple + */ + if (resultRelInfo->ri_NumIndices > 0) + recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false); + + /* AFTER ROW INSERT Triggers */ + ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes); + + /* Process RETURNING if present */ + if (resultRelInfo->ri_projectReturning) + slot = ExecProcessReturning(resultRelInfo->ri_projectReturning, + slot, planSlot); + + return slot; + } + + /* ---------------------------------------------------------------- + * ExecDelete + * + * DELETE is like UPDATE, except that we delete the tuple and no + * index modifications are needed + * ---------------------------------------------------------------- + */ + static TupleTableSlot * + ExecDelete(ItemPointer tupleid, + TupleTableSlot *planSlot, + EState *estate) + { + ResultRelInfo* resultRelInfo; + Relation resultRelationDesc; + HTSU_Result result; + ItemPointerData update_ctid; + TransactionId update_xmax; + + /* + * get information on the (current) result relation + */ + resultRelInfo = estate->es_result_relation_info; + resultRelationDesc = resultRelInfo->ri_RelationDesc; + + /* BEFORE ROW DELETE Triggers */ + if (resultRelInfo->ri_TrigDesc && + resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0) + { + bool dodelete; + + dodelete = ExecBRDeleteTriggers(estate, resultRelInfo, tupleid); + + if (!dodelete) /* "do nothing" */ + return planSlot; + } + + /* + * delete the tuple + * + * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that + * the row to be deleted is visible to that snapshot, and throw a can't- + * serialize error if not. This is a special-case behavior needed for + * referential integrity updates in serializable transactions. + */ + ldelete:; + result = heap_delete(resultRelationDesc, tupleid, + &update_ctid, &update_xmax, + estate->es_output_cid, + estate->es_crosscheck_snapshot, + true /* wait for commit */ ); + switch (result) + { + case HeapTupleSelfUpdated: + /* already deleted by self; nothing to do */ + return planSlot; + + case HeapTupleMayBeUpdated: + break; + + case HeapTupleUpdated: + if (IsXactIsoLevelSerializable) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); + else if (!ItemPointerEquals(tupleid, &update_ctid)) + { + TupleTableSlot *epqslot; + + epqslot = EvalPlanQual(estate, + resultRelInfo->ri_RangeTableIndex, + &update_ctid, + update_xmax); + if (!TupIsNull(epqslot)) + { + *tupleid = update_ctid; + goto ldelete; + } + } + /* tuple already deleted; nothing to do */ + return planSlot; + + default: + elog(ERROR, "unrecognized heap_delete status: %u", result); + return NULL; + } + + IncrDeleted(); + (estate->es_processed)++; + + /* + * Note: Normally one would think that we have to delete index tuples + * associated with the heap tuple now... + * + * ... but in POSTGRES, we have no need to do this because VACUUM will + * take care of it later. We can't delete index tuples immediately + * anyway, since the tuple is still visible to other transactions. + */ + + /* AFTER ROW DELETE Triggers */ + ExecARDeleteTriggers(estate, resultRelInfo, tupleid); + + /* Process RETURNING if present */ + if (resultRelInfo->ri_projectReturning) + { + /* + * We have to put the target tuple into a slot, which means first we + * gotta fetch it. We can use the trigger tuple slot. + */ + TupleTableSlot *slot = estate->es_trig_tuple_slot; + HeapTupleData deltuple; + Buffer delbuffer; + + deltuple.t_self = *tupleid; + if (!heap_fetch(resultRelationDesc, SnapshotAny, + &deltuple, &delbuffer, false, NULL)) + elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING"); + + if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc)) + ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc)); + ExecStoreTuple(&deltuple, slot, InvalidBuffer, false); + + planSlot = ExecProcessReturning(resultRelInfo->ri_projectReturning, + slot, planSlot); + + ExecClearTuple(slot); + ReleaseBuffer(delbuffer); + } + + return planSlot; + } + + /* ---------------------------------------------------------------- + * ExecUpdate + * + * note: we can't run UPDATE queries with transactions + * off because UPDATEs are actually INSERTs and our + * scan will mistakenly loop forever, updating the tuple + * it just inserted.. This should be fixed but until it + * is, we don't want to get stuck in an infinite loop + * which corrupts your database.. + * ---------------------------------------------------------------- + */ + static TupleTableSlot * + ExecUpdate(TupleTableSlot *slot, + ItemPointer tupleid, + TupleTableSlot *planSlot, + EState *estate) + { + HeapTuple tuple; + ResultRelInfo *resultRelInfo; + Relation resultRelationDesc; + HTSU_Result result; + ItemPointerData update_ctid; + TransactionId update_xmax; + List *recheckIndexes = NIL; + + /* + * abort the operation if not running transactions + */ + if (IsBootstrapProcessingMode()) + elog(ERROR, "cannot UPDATE during bootstrap"); + + /* + * get the heap tuple out of the tuple table slot, making sure we have a + * writable copy + */ + tuple = ExecMaterializeSlot(slot); + + /* + * get information on the (current) result relation + */ + resultRelInfo = estate->es_result_relation_info; + resultRelationDesc = resultRelInfo->ri_RelationDesc; + + /* BEFORE ROW UPDATE Triggers */ + if (resultRelInfo->ri_TrigDesc && + resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0) + { + HeapTuple newtuple; + + newtuple = ExecBRUpdateTriggers(estate, resultRelInfo, + tupleid, tuple); + + if (newtuple == NULL) /* "do nothing" */ + return planSlot; + + if (newtuple != tuple) /* modified by Trigger(s) */ + { + /* + * Put the modified tuple into a slot for convenience of routines + * below. We assume the tuple was allocated in per-tuple memory + * context, and therefore will go away by itself. The tuple table + * slot should not try to clear it. + */ + TupleTableSlot *newslot = estate->es_trig_tuple_slot; + + if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor) + ExecSetSlotDescriptor(newslot, slot->tts_tupleDescriptor); + ExecStoreTuple(newtuple, newslot, InvalidBuffer, false); + slot = newslot; + tuple = newtuple; + } + } + + /* + * Check the constraints of the tuple + * + * If we generate a new candidate tuple after EvalPlanQual testing, we + * must loop back here and recheck constraints. (We don't need to redo + * triggers, however. If there are any BEFORE triggers then trigger.c + * will have done heap_lock_tuple to lock the correct tuple, so there's no + * need to do them again.) + */ + lreplace:; + if (resultRelationDesc->rd_att->constr) + ExecConstraints(resultRelInfo, slot, estate); + + /* + * replace the heap tuple + * + * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that + * the row to be updated is visible to that snapshot, and throw a can't- + * serialize error if not. This is a special-case behavior needed for + * referential integrity updates in serializable transactions. + */ + result = heap_update(resultRelationDesc, tupleid, tuple, + &update_ctid, &update_xmax, + estate->es_output_cid, + estate->es_crosscheck_snapshot, + true /* wait for commit */ ); + switch (result) + { + case HeapTupleSelfUpdated: + /* already deleted by self; nothing to do */ + return planSlot; + + case HeapTupleMayBeUpdated: + break; + + case HeapTupleUpdated: + if (IsXactIsoLevelSerializable) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); + else if (!ItemPointerEquals(tupleid, &update_ctid)) + { + TupleTableSlot *epqslot; + + epqslot = EvalPlanQual(estate, + resultRelInfo->ri_RangeTableIndex, + &update_ctid, + update_xmax); + if (!TupIsNull(epqslot)) + { + *tupleid = update_ctid; + slot = ExecFilterJunk(estate->es_result_relation_info->ri_junkFilter, epqslot); + tuple = ExecMaterializeSlot(slot); + goto lreplace; + } + } + /* tuple already deleted; nothing to do */ + return planSlot; + + default: + elog(ERROR, "unrecognized heap_update status: %u", result); + return NULL; + } + + IncrReplaced(); + (estate->es_processed)++; + + /* + * Note: instead of having to update the old index tuples associated with + * the heap tuple, all we do is form and insert new index tuples. This is + * because UPDATEs are actually DELETEs and INSERTs, and index tuple + * deletion is done later by VACUUM (see notes in ExecDelete). All we do + * here is insert new index tuples. -cim 9/27/89 + */ + + /* + * insert index entries for tuple + * + * Note: heap_update returns the tid (location) of the new tuple in the + * t_self field. + * + * If it's a HOT update, we mustn't insert new index entries. + */ + if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple)) + recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), + estate, false); + + /* AFTER ROW UPDATE Triggers */ + ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple, + recheckIndexes); + + /* Process RETURNING if present */ + if (resultRelInfo->ri_projectReturning) + slot = ExecProcessReturning(resultRelInfo->ri_projectReturning, + slot, planSlot); + + return slot; + } + + TupleTableSlot * + ExecDml(DmlState *node) + { + CmdType operation = node->operation; + EState *estate = node->ps.state; + JunkFilter *junkfilter; + TupleTableSlot *slot; + TupleTableSlot *planSlot; + ItemPointer tupleid = NULL; + ItemPointerData tuple_ctid; + + for (;;) + { + planSlot = ExecProcNode(node->dmlplans[node->ds_whichplan]); + if (TupIsNull(planSlot)) + { + node->ds_whichplan++; + if (node->ds_whichplan < node->ds_nplans) + { + estate->es_result_relation_info++; + continue; + } + else + return NULL; + } + else + break; + } + + slot = planSlot; + + if ((junkfilter = estate->es_result_relation_info->ri_junkFilter) != NULL) + { + /* + * extract the 'ctid' junk attribute. + */ + if (operation == CMD_UPDATE || operation == CMD_DELETE) + { + Datum datum; + bool isNull; + + datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, + &isNull); + /* shouldn't ever get a null result... */ + if (isNull) + elog(ERROR, "ctid is NULL"); + + tupleid = (ItemPointer) DatumGetPointer(datum); + tuple_ctid = *tupleid; /* make sure we don't free the ctid!! */ + tupleid = &tuple_ctid; + } + + if (operation != CMD_DELETE) + slot = ExecFilterJunk(junkfilter, slot); + } + + switch (operation) + { + case CMD_INSERT: + return ExecInsert(slot, tupleid, planSlot, estate); + break; + case CMD_UPDATE: + return ExecUpdate(slot, tupleid, planSlot, estate); + break; + case CMD_DELETE: + return ExecDelete(tupleid, slot, estate); + default: + elog(ERROR, "unknown operation"); + break; + } + + return NULL; + } + + DmlState * + ExecInitDml(Dml *node, EState *estate, int eflags) + { + DmlState *dmlstate; + ResultRelInfo *resultRelInfo; + Plan *subplan; + ListCell *l; + ListCell *relindex; + CmdType operation = node->operation; + int i; + + TupleDesc tupDesc; + + /* + * create state structure + */ + dmlstate = makeNode(DmlState); + dmlstate->ps.plan = (Plan *) node; + dmlstate->ps.state = estate; + dmlstate->ps.targetlist = node->plan.targetlist; + + dmlstate->ds_nplans = list_length(node->plans); + dmlstate->dmlplans = (PlanState **) palloc0(sizeof(PlanState *) * dmlstate->ds_nplans); + dmlstate->operation = node->operation; + + estate->es_result_relation_info = estate->es_result_relations; + relindex = list_head(node->resultRelations); + i = 0; + foreach(l, node->plans) + { + subplan = lfirst(l); + + dmlstate->dmlplans[i] = ExecInitNode(subplan, estate, eflags); + + i++; + estate->es_result_relation_info++; + relindex = lnext(relindex); + } + + estate->es_result_relation_info = estate->es_result_relations; + + dmlstate->ds_whichplan = 0; + + subplan = (Plan *) linitial(node->plans); + + /* Initialize targetlist for RETURNING */ + if (node->returningLists) + { + TupleTableSlot *slot; + ExprContext *econtext; + + /* + * Initialize result tuple slot and assign + * type from the target list. + */ + tupDesc = ExecTypeFromTL((List *) linitial(node->returningLists), + false); + + /* + * Set up a slot for the output of the RETURNING projection(s). + */ + slot = ExecAllocTableSlot(estate->es_tupleTable); + ExecSetSlotDescriptor(slot, tupDesc); + + econtext = CreateExprContext(estate); + + Assert(list_length(node->returningLists) == estate->es_num_result_relations); + resultRelInfo = estate->es_result_relations; + foreach(l, node->returningLists) + { + List *rlist = (List *) lfirst(l); + List *rliststate; + + rliststate = (List *) ExecInitExpr((Expr *) rlist, &dmlstate->ps); + resultRelInfo->ri_projectReturning = + ExecBuildProjectionInfo(rliststate, econtext, slot, + resultRelInfo->ri_RelationDesc->rd_att); + resultRelInfo++; + } + + dmlstate->ps.targetlist = estate->es_result_relation_info->ri_projectReturning->pi_targetlist; + dmlstate->ps.ps_ResultTupleSlot = slot; + dmlstate->ps.ps_ExprContext = econtext; + } + else + { + ExecInitResultTupleSlot(estate, &dmlstate->ps); + tupDesc = ExecTypeFromTL(subplan->targetlist, false); + ExecAssignResultType(&dmlstate->ps, tupDesc); + + dmlstate->ps.targetlist = subplan->targetlist; + dmlstate->ps.ps_ExprContext = NULL; + } + + /* + * Initialize the junk filter if needed. INSERT queries need a filter + * if there are any junk attrs in the tlist. UPDATE and DELETE + * always need a filter, since there's always a junk 'ctid' attribute + * present --- no need to look first. + * + * This section of code is also a convenient place to verify that the + * output of an INSERT or UPDATE matches the target table(s). + */ + { + bool junk_filter_needed = false; + ListCell *tlist; + + switch (operation) + { + case CMD_INSERT: + foreach(tlist, subplan->targetlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tlist); + + if (tle->resjunk) + { + junk_filter_needed = true; + break; + } + } + break; + case CMD_UPDATE: + case CMD_DELETE: + junk_filter_needed = true; + break; + default: + break; + } + + if (junk_filter_needed) + { + /* + * If there are multiple result relations, each one needs its own + * junk filter. Note this is only possible for UPDATE/DELETE, so + * we can't be fooled by some needing a filter and some not. + + */ + if (dmlstate->ds_nplans > 1) + { + resultRelInfo = estate->es_result_relations; + for (i = 0; i < dmlstate->ds_nplans; i++) + { + PlanState *ps = dmlstate->dmlplans[i]; + JunkFilter *j; + + if (operation == CMD_UPDATE) + ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, + ps->plan->targetlist); + + j = ExecInitJunkFilter(ps->plan->targetlist, + resultRelInfo->ri_RelationDesc->rd_att->tdhasoid, + ExecAllocTableSlot(estate->es_tupleTable)); + + + /* + * Since it must be UPDATE/DELETE, there had better be a + * "ctid" junk attribute in the tlist ... but ctid could + * be at a different resno for each result relation. We + * look up the ctid resnos now and save them in the + * junkfilters. + */ + j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); + if (!AttributeNumberIsValid(j->jf_junkAttNo)) + elog(ERROR, "could not find junk ctid column"); + resultRelInfo->ri_junkFilter = j; + resultRelInfo++; + } + } + else + { + JunkFilter *j; + subplan = dmlstate->dmlplans[0]->plan; + + if (operation == CMD_INSERT || operation == CMD_UPDATE) + ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc, + subplan->targetlist); + + j = ExecInitJunkFilter(subplan->targetlist, estate->es_result_relation_info->ri_RelationDesc->rd_att->tdhasoid, + ExecAllocTableSlot(estate->es_tupleTable)); + + if (operation == CMD_UPDATE || operation == CMD_DELETE) + { + /* FOR UPDATE/DELETE, find the ctid junk attr now */ + j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); + if (!AttributeNumberIsValid(j->jf_junkAttNo)) + elog(ERROR, "could not find junk ctid column"); + } + + estate->es_result_relation_info->ri_junkFilter = j; + } + } + else + { + if (operation == CMD_INSERT) + ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc, + ((Plan *) linitial(((Dml *) dmlstate->ps.plan)->plans))->targetlist); + } + } + + return dmlstate; + } + + int + ExecCountSlotsDml(Dml *node) + { + ListCell* l; + int nslots = 5; + + foreach(l, node->plans) + nslots += ExecCountSlotsNode((Plan *) lfirst(l)); + + return nslots; + } + + void + ExecEndDml(DmlState *node) + { + int i; + + /* + * Free the exprcontext + */ + ExecFreeExprContext(&node->ps); + + /* + * clean out the tuple table + */ + ExecClearTuple(node->ps.ps_ResultTupleSlot); + + /* + * shut down subplans + */ + for (i=0;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 ---- *************** *** 1391,1396 **** _copyXmlExpr(XmlExpr *from) --- 1390,1411 ---- return newnode; } + + static Dml * + _copyDml(Dml *from) + { + Dml *newnode = makeNode(Dml); + + CopyPlanFields((Plan *) from, (Plan *) newnode); + + COPY_NODE_FIELD(plans); + COPY_SCALAR_FIELD(operation); + COPY_NODE_FIELD(resultRelations); + COPY_NODE_FIELD(returningLists); + + return newnode; + } + /* * _copyNullIfExpr (same as OpExpr) *************** *** 4083,4088 **** copyObject(void *from) --- 4098,4106 ---- case T_XmlSerialize: retval = _copyXmlSerialize(from); break; + case T_Dml: + retval = _copyDml(from); + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from)); *** a/src/backend/nodes/outfuncs.c --- b/src/backend/nodes/outfuncs.c *************** *** 326,332 **** _outAppend(StringInfo str, Append *node) _outPlanInfo(str, (Plan *) node); WRITE_NODE_FIELD(appendplans); - WRITE_BOOL_FIELD(isTarget); } static void --- 326,331 ---- *** a/src/backend/optimizer/plan/createplan.c --- b/src/backend/optimizer/plan/createplan.c *************** *** 574,580 **** create_append_plan(PlannerInfo *root, AppendPath *best_path) subplans = lappend(subplans, create_plan(root, subpath)); } ! plan = make_append(subplans, false, tlist); return (Plan *) plan; } --- 574,580 ---- subplans = lappend(subplans, create_plan(root, subpath)); } ! plan = make_append(subplans, tlist); return (Plan *) plan; } *************** *** 2616,2622 **** make_worktablescan(List *qptlist, } Append * ! make_append(List *appendplans, bool isTarget, List *tlist) { Append *node = makeNode(Append); Plan *plan = &node->plan; --- 2616,2622 ---- } Append * ! make_append(List *appendplans, List *tlist) { Append *node = makeNode(Append); Plan *plan = &node->plan; *************** *** 2652,2658 **** make_append(List *appendplans, bool isTarget, List *tlist) plan->lefttree = NULL; plan->righttree = NULL; node->appendplans = appendplans; - node->isTarget = isTarget; return node; } --- 2652,2657 ---- *************** *** 3659,3664 **** make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount, --- 3658,3689 ---- return node; } + Dml * + make_dml(List *subplans, List *returningLists, List *resultRelations, CmdType operation) + { + Dml *node = makeNode(Dml); + + Assert(list_length(subplans) == list_length(resultRelations)); + Assert(!returningLists || list_length(returningLists) == list_length(resultRelations)); + + node->plan.lefttree = NULL; + node->plan.righttree = NULL; + node->plan.qual = NIL; + + if (returningLists) + node->plan.targetlist = linitial(returningLists); + else + node->plan.targetlist = NIL; + + node->plans = subplans; + node->resultRelations = resultRelations; + node->returningLists = returningLists; + + node->operation = operation; + + return node; + } + /* * make_result * Build a Result plan node *** a/src/backend/optimizer/plan/planner.c --- b/src/backend/optimizer/plan/planner.c *************** *** 478,485 **** subquery_planner(PlannerGlobal *glob, Query *parse, --- 478,494 ---- rt_fetch(parse->resultRelation, parse->rtable)->inh) plan = inheritance_planner(root); else + { plan = grouping_planner(root, tuple_fraction); + if (parse->commandType != CMD_SELECT) + plan = (Plan *) make_dml(list_make1(plan), + root->returningLists, + root->resultRelations, + parse->commandType); + } + + /* * If any subplans were generated, or if we're inside a subplan, build * initPlan list and extParam/allParam sets for plan nodes, and attach the *************** *** 624,632 **** preprocess_qual_conditions(PlannerInfo *root, Node *jtnode) * is an inheritance set. Source inheritance is expanded at the bottom of the * plan tree (see allpaths.c), but target inheritance has to be expanded at * the top. The reason is that for UPDATE, each target relation needs a ! * different targetlist matching its own column set. Also, for both UPDATE ! * and DELETE, the executor needs the Append plan node at the top, else it ! * can't keep track of which table is the current target table. Fortunately, * the UPDATE/DELETE target can never be the nullable side of an outer join, * so it's OK to generate the plan this way. * --- 633,639 ---- * is an inheritance set. Source inheritance is expanded at the bottom of the * plan tree (see allpaths.c), but target inheritance has to be expanded at * the top. The reason is that for UPDATE, each target relation needs a ! * different targetlist matching its own column set. Fortunately, * the UPDATE/DELETE target can never be the nullable side of an outer join, * so it's OK to generate the plan this way. * *************** *** 737,747 **** inheritance_planner(PlannerInfo *root) */ parse->rtable = rtable; ! /* Suppress Append if there's only one surviving child rel */ ! if (list_length(subplans) == 1) ! return (Plan *) linitial(subplans); ! ! return (Plan *) make_append(subplans, true, tlist); } /*-------------------- --- 744,753 ---- */ parse->rtable = rtable; ! return (Plan *) make_dml(subplans, ! root->returningLists, ! root->resultRelations, ! parse->commandType); } /*-------------------- *** a/src/backend/optimizer/plan/setrefs.c --- b/src/backend/optimizer/plan/setrefs.c *************** *** 375,380 **** set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset) --- 375,403 ---- set_join_references(glob, (Join *) plan, rtoffset); break; + case T_Dml: + { + /* + * grouping_planner() already called set_returning_clause_references + * so the targetList's references are already set. + */ + Dml *splan = (Dml *) plan; + + foreach(l, splan->resultRelations) + { + lfirst_int(l) += rtoffset; + } + + Assert(splan->plan.qual == NIL); + foreach(l, splan->plans) + { + lfirst(l) = set_plan_refs(glob, + (Plan *) lfirst(l), + rtoffset); + } + } + break; + case T_Hash: case T_Material: case T_Sort: *** a/src/backend/optimizer/plan/subselect.c --- b/src/backend/optimizer/plan/subselect.c *************** *** 2034,2039 **** finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params) --- 2034,2040 ---- case T_Unique: case T_SetOp: case T_Group: + case T_Dml: break; default: *** a/src/backend/optimizer/prep/prepunion.c --- b/src/backend/optimizer/prep/prepunion.c *************** *** 448,454 **** generate_union_plan(SetOperationStmt *op, PlannerInfo *root, /* * Append the child results together. */ ! plan = (Plan *) make_append(planlist, false, tlist); /* * For UNION ALL, we just need the Append plan. For UNION, need to add --- 448,454 ---- /* * Append the child results together. */ ! plan = (Plan *) make_append(planlist, tlist); /* * For UNION ALL, we just need the Append plan. For UNION, need to add *************** *** 539,545 **** generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root, /* * Append the child results together. */ ! plan = (Plan *) make_append(planlist, false, tlist); /* Identify the grouping semantics */ groupList = generate_setop_grouplist(op, tlist); --- 539,545 ---- /* * Append the child results together. */ ! plan = (Plan *) make_append(planlist, tlist); /* Identify the grouping semantics */ groupList = generate_setop_grouplist(op, tlist); *** /dev/null --- b/src/include/executor/nodeDml.h *************** *** 0 **** --- 1,11 ---- + #ifndef NODEDML_H + #define NODEDML_H + + #include "nodes/execnodes.h" + + extern int ExecCountSlotsDml(Dml *node); + extern DmlState *ExecInitDml(Dml *node, EState *estate, int eflags); + extern TupleTableSlot *ExecDml(DmlState *node); + extern void ExecEndDml(DmlState *node); + + #endif *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** *** 976,981 **** typedef struct ResultState --- 976,996 ---- } ResultState; /* ---------------- + * DmlState information + * ---------------- + */ + typedef struct DmlState + { + PlanState ps; /* its first field is NodeTag */ + PlanState **dmlplans; + int ds_nplans; + int ds_whichplan; + + CmdType operation; + } DmlState; + + + /* ---------------- * AppendState information * * nplans how many plans are in the list *** a/src/include/nodes/nodes.h --- b/src/include/nodes/nodes.h *************** *** 71,76 **** typedef enum NodeTag --- 71,77 ---- T_Hash, T_SetOp, T_Limit, + T_Dml, /* this one isn't a subclass of Plan: */ T_PlanInvalItem, *************** *** 190,195 **** typedef enum NodeTag --- 191,197 ---- T_NullTestState, T_CoerceToDomainState, T_DomainConstraintState, + T_DmlState, /* * TAGS FOR PLANNER NODES (relation.h) *** a/src/include/nodes/plannodes.h --- b/src/include/nodes/plannodes.h *************** *** 164,185 **** typedef struct Result Node *resconstantqual; } Result; /* ---------------- * Append node - * Generate the concatenation of the results of sub-plans. - * - * Append nodes are sometimes used to switch between several result relations - * (when the target of an UPDATE or DELETE is an inheritance set). Such a - * node will have isTarget true. The Append executor is then responsible - * for updating the executor state to point at the correct target relation - * whenever it switches subplans. * ---------------- */ typedef struct Append { Plan plan; List *appendplans; - bool isTarget; } Append; /* ---------------- --- 164,189 ---- Node *resconstantqual; } Result; + typedef struct Dml + { + Plan plan; + + CmdType operation; + List *plans; + List *resultRelations; + List *returningLists; + } Dml; + + /* ---------------- * Append node - * Generate the concatenation of the results of sub-plans. * ---------------- */ typedef struct Append { Plan plan; List *appendplans; } Append; /* ---------------- *** a/src/include/optimizer/planmain.h --- b/src/include/optimizer/planmain.h *************** *** 41,47 **** extern Plan *optimize_minmax_aggregates(PlannerInfo *root, List *tlist, extern Plan *create_plan(PlannerInfo *root, Path *best_path); extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual, Index scanrelid, Plan *subplan, List *subrtable); ! extern Append *make_append(List *appendplans, bool isTarget, List *tlist); extern RecursiveUnion *make_recursive_union(List *tlist, Plan *lefttree, Plan *righttree, int wtParam, List *distinctList, long numGroups); --- 41,47 ---- extern Plan *create_plan(PlannerInfo *root, Path *best_path); extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual, Index scanrelid, Plan *subplan, List *subrtable); ! extern Append *make_append(List *appendplans, List *tlist); extern RecursiveUnion *make_recursive_union(List *tlist, Plan *lefttree, Plan *righttree, int wtParam, List *distinctList, long numGroups); *************** *** 69,74 **** extern Plan *materialize_finished_plan(Plan *subplan); --- 69,76 ---- extern Unique *make_unique(Plan *lefttree, List *distinctList); extern Limit *make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount, int64 offset_est, int64 count_est); + extern Dml *make_dml(List *subplans, List *returningLists, List *resultRelation, + CmdType operation); extern SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree, List *distinctList, AttrNumber flagColIdx, int firstFlag, long numGroups, double outputRows);