*** a/src/backend/commands/explain.c --- b/src/backend/commands/explain.c *************** *** 670,675 **** ExplainNode(Plan *plan, PlanState *planstate, --- 670,692 ---- case T_Hash: pname = "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 = "???"; break; *************** *** 843,848 **** ExplainNode(Plan *plan, PlanState *planstate, --- 860,870 ---- ((AppendState *) planstate)->appendplans, outer_plan, indent, es); break; + case T_Dml: + ExplainMemberNodes(((Dml *) plan)->plans, + ((DmlState *) planstate)->dmlplans, + outer_plan, indent, 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 *************** *** 679,701 **** InitPlan(QueryDesc *queryDesc, int eflags) resultRelInfos = (ResultRelInfo *) palloc(numResultRelations * sizeof(ResultRelInfo)); ! resultRelInfo = resultRelInfos; ! foreach(l, resultRelations) ! { ! Index resultRelationIndex = lfirst_int(l); ! Oid resultRelationOid; ! Relation resultRelation; ! ! resultRelationOid = getrelid(resultRelationIndex, rangeTable); ! resultRelation = heap_open(resultRelationOid, RowExclusiveLock); ! InitResultRelInfo(resultRelInfo, ! resultRelation, ! resultRelationIndex, ! operation, ! estate->es_instrument); ! resultRelInfo++; ! } estate->es_result_relations = resultRelInfos; estate->es_num_result_relations = numResultRelations; /* Initialize to first or only result rel */ estate->es_result_relation_info = resultRelInfos; --- 679,687 ---- resultRelInfos = (ResultRelInfo *) palloc(numResultRelations * sizeof(ResultRelInfo)); ! estate->es_result_relations = resultRelInfos; + estate->es_num_result_relations = numResultRelations; /* Initialize to first or only result rel */ estate->es_result_relation_info = resultRelInfos; *************** *** 879,937 **** InitPlan(QueryDesc *queryDesc, int eflags) 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 --- 865,872 ---- *************** *** 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)); --- 879,886 ---- *************** *** 963,975 **** InitPlan(QueryDesc *queryDesc, int eflags) /* 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) --- 893,898 ---- *************** *** 999,1008 **** 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"); --- 922,927 ---- *************** *** 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, --- 1070,1075 ---- *************** *** 1463,1468 **** ExecutePlan(EState *estate, --- 1313,1319 ---- */ estate->es_direction = direction; + #if 0 /* * Process BEFORE EACH STATEMENT triggers */ *************** *** 1481,1486 **** ExecutePlan(EState *estate, --- 1332,1338 ---- /* do nothing */ break; } + #endif /* * Loop until we've processed the proper number of tuples from the plan. *************** *** 1630,1635 **** lnext: ; --- 1482,1488 ---- } } + #if 0 /* * extract the 'ctid' junk attribute. */ *************** *** 1656,1661 **** lnext: ; --- 1509,1515 ---- */ if (operation != CMD_DELETE) slot = ExecFilterJunk(junkfilter, slot); + #endif } /* *************** *** 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: --- 1524,1538 ---- 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: *************** *** 1697,1702 **** lnext: ; --- 1551,1557 ---- break; } + #if 0 /* * Process AFTER EACH STATEMENT triggers */ *************** *** 1715,1720 **** lnext: ; --- 1570,1576 ---- /* do nothing */ break; } + #endif } /* ---------------------------------------------------------------- *** 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; *** /dev/null --- b/src/backend/executor/nodeDml.c *************** *** 0 **** --- 1,641 ---- + #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" + + + /* + * 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 * + 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_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); + + #if 0 + /* Process RETURNING if present */ + if (resultRelInfo->ri_projectReturning) + ExecProcessReturning(resultRelInfo->ri_projectReturning, + slot, planSlot, dest); + #endif + + 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, + ResultRelInfo *resultRelInfo, + EState *estate) + { + Relation resultRelationDesc; + HTSU_Result result; + ItemPointerData update_ctid; + TransactionId update_xmax; + + /* + * get information on the (current) result relation + */ + 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 NULL; + } + + /* + * 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. + */ + + return planSlot; + + #if 0 + /* 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); + } + #endif + } + + + + 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) + { + node->ds_result_relation_info++; + continue; + } + else + return NULL; + } + else + break; + } + + slot = planSlot; + + if ((junkfilter = node->ds_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, NULL, + estate); + break; + case CMD_UPDATE: + break; + case CMD_DELETE: + return ExecDelete(tupleid, planSlot, node->ds_result_relation_info, estate); + default: + break; + } + + return NULL; + } + + DmlState * + ExecInitDml(Dml *node, EState *estate, int eflags) + { + DmlState *resstate; + ResultRelInfo *resultRelInfo; + Relation resultRelation; + Plan *subplan; + ListCell *l; + ListCell *relindex; + CmdType operation = node->operation; + int i; + + TupleDesc tupDesc; + + /* + * create state structure + */ + resstate = makeNode(DmlState); + resstate->ps.plan = (Plan *) node; + resstate->ps.state = estate; + resstate->ps.targetlist = node->plan.targetlist; + + resstate->ds_nplans = list_length(node->plans); + resstate->ds_result_relations = estate->es_result_relations; + resstate->ds_result_relation_info = resstate->ds_result_relations; + resstate->dmlplans = (PlanState **) palloc(sizeof(PlanState *) * resstate->ds_nplans); + resstate->operation = node->operation; + + resultRelInfo = estate->es_result_relations; + relindex = list_head(node->resultRelations); + i = 0; + foreach(l, node->plans) + { + Oid resultRelationOid; + Index resultRelationIndex; + + subplan = lfirst(l); + + resultRelationIndex = lfirst_int(relindex); + resultRelationOid = getrelid(resultRelationIndex, estate->es_range_table); + resultRelation = heap_open(resultRelationOid, RowExclusiveLock); + InitResultRelInfo(resultRelInfo, + resultRelation, + resultRelationIndex, + operation, + estate->es_instrument); + resstate->dmlplans[i] = ExecInitNode(subplan, estate, eflags); + + i++; + resultRelInfo++; + relindex = lnext(relindex); + } + + resstate->ds_whichplan = 0; + + subplan = (Plan *) linitial(node->plans); + + /* Initialize targetlist for RETURNING */ + if (resstate->ps.targetlist) + { + /* + * Initialize result tuple slot and assign + * type from the target list. + */ + ExecInitResultTupleSlot(estate, &resstate->ps); + ExecAssignResultTypeFromTL(&resstate->ps); + + /* + * Prepare the RETURNING expression tree for execution. This + * has to be done after calling ExecAssignResultTypeFromTL(). + */ + resstate->ps.targetlist = (List *) + ExecInitExpr((Expr *) node->plan.targetlist, + (PlanState *) resstate); + resstate->ps.ps_ExprContext = CreateExprContext(estate); + } + else + { + ExecInitResultTupleSlot(estate, &resstate->ps); + tupDesc = ExecTypeFromTL(subplan->targetlist, false); + ExecAssignResultType(&resstate->ps, tupDesc); + + resstate->ps.targetlist = NIL; + resstate->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 (list_length(node->plans) > 1) + { + resultRelInfo = resstate->ds_result_relations; + for (i = 0; i < resstate->ds_nplans; i++) + { + PlanState *ps = resstate->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; + + #if 0 + if (operation == CMD_INSERT || operation == CMD_UPDATE) + ExecCheckPlanOutput(); + #endif + + j = ExecInitJunkFilter(subplan->targetlist, tupDesc->tdhasoid, + ExecAllocTableSlot(estate->es_tupleTable)); + + if (operation == CMD_UPDATE || operation == CMD_DELETE) + { + j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); + if (!AttributeNumberIsValid(j->jf_junkAttNo)) + elog(ERROR, "could not find junk ctid column"); + } + + resstate->ds_result_relation_info->ri_junkFilter = j; + + 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"); + } + } + } + else + { + if (operation == CMD_INSERT) + ExecCheckPlanOutput(resstate->ds_result_relation_info->ri_RelationDesc, + ((Plan *) linitial(((Dml *) resstate->ps.plan)->plans))->targetlist); + } + } + + return resstate; + } + + int + ExecCountSlotsDml(Dml *node) + { + int nslots = 2; + ListCell* l; + + foreach(l, node->plans) + { + nslots += ExecCountSlotsNode((Plan *) lfirst(l)); + } + + return nslots; + } + + void + ExecEndDml(DmlState *node) + { + ResultRelInfo* resultRelInfo = node->ds_result_relations; + 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) + { + heap_close(resultRelInfo->ri_RelationDesc, NoLock); + resultRelInfo++; + ExecEndNode(node->dmlplans[i]); + } + + pfree(node->dmlplans); + } *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 1391,1396 **** _copyXmlExpr(XmlExpr *from) --- 1391,1410 ---- return newnode; } + + static Dml * + _copyDml(Dml *from) + { + Dml *newnode = makeNode(Dml); + + CopyPlanFields((Plan *) from, (Plan *) newnode); + newnode->operation = from->operation; + newnode->plans = from->plans; + newnode->resultRelations = from->resultRelations; + + return newnode; + } + /* * _copyNullIfExpr (same as OpExpr) *************** *** 4083,4088 **** copyObject(void *from) --- 4097,4105 ---- 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/optimizer/plan/createplan.c --- b/src/backend/optimizer/plan/createplan.c *************** *** 3659,3664 **** make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount, --- 3659,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; + + node->plans = subplans; + node->resultRelations = resultRelations; + + if (returningLists != NIL) + node->plan.targetlist = linitial(returningLists); + else + node->plan.targetlist = NIL; + + 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 *************** *** 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); } /*-------------------- --- 737,749 ---- */ parse->rtable = rtable; + #if 0 /* Suppress Append if there's only one surviving child rel */ if (list_length(subplans) == 1) return (Plan *) linitial(subplans); + #endif ! return (Plan *) make_dml(subplans, returningLists, resultRelations, parse->commandType); } /*-------------------- *************** *** 1568,1597 **** grouping_planner(PlannerInfo *root, double tuple_fraction) count_est); } ! /* ! * Deal with the RETURNING clause if any. It's convenient to pass the ! * returningList through setrefs.c now rather than at top level (if we ! * waited, handling inherited UPDATE/DELETE would be much harder). ! */ ! if (parse->returningList) { ! List *rlist; ! ! Assert(parse->resultRelation); ! rlist = set_returning_clause_references(root->glob, ! parse->returningList, ! result_plan, ! parse->resultRelation); ! root->returningLists = list_make1(rlist); ! } ! else ! root->returningLists = NIL; ! /* Compute result-relations list if needed */ ! if (parse->resultRelation) ! root->resultRelations = list_make1_int(parse->resultRelation); ! else ! root->resultRelations = NIL; /* * Return the actual output ordering in query_pathkeys for possible use by --- 1570,1607 ---- count_est); } ! if (parse->resultRelation && !root->append_rel_list) { ! /* ! * Deal with the RETURNING clause if any. It's convenient to pass the ! * returningList through setrefs.c now rather than at top level (if we ! * waited, handling inherited UPDATE/DELETE would be much harder). ! */ ! if (parse->returningList) ! { ! List *rlist; ! ! Assert(parse->resultRelation); ! rlist = set_returning_clause_references(root->glob, ! parse->returningList, ! result_plan, ! parse->resultRelation); ! root->returningLists = list_make1(rlist); ! } ! else ! root->returningLists = NIL; ! /* Compute result-relations list if needed */ ! if (parse->resultRelation) ! root->resultRelations = list_make1_int(parse->resultRelation); ! else ! root->resultRelations = NIL; ! ! result_plan = (Plan *) make_dml(list_make1(result_plan), ! root->returningLists, ! root->resultRelations, ! parse->commandType); ! } /* * Return the actual output ordering in query_pathkeys for possible use by *** 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,396 ---- 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; + ListCell *l; + + foreach(l, splan->resultRelations) + { + lfirst_int(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: *** /dev/null --- b/src/include/executor/nodeDml.h *************** *** 0 **** --- 1,11 ---- + #ifndef NODEINSERT_H + #define NODEINSERT_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 *************** *** 978,983 **** typedef struct ResultState --- 978,1001 ---- } ResultState; /* ---------------- + * DmlState information + * ---------------- + */ + typedef struct DmlState + { + PlanState ps; /* its first field is NodeTag */ + PlanState **dmlplans; + int ds_nplans; + int ds_whichplan; + + CmdType operation; + + ResultRelInfo *ds_result_relations; + ResultRelInfo *ds_result_relation_info; + } 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,169 **** typedef struct Result --- 164,179 ---- Node *resconstantqual; } Result; + typedef struct Dml + { + Plan plan; + + CmdType operation; + List *plans; + List *resultRelations; + } Dml; + + /* ---------------- * Append node - * Generate the concatenation of the results of sub-plans.