From 4b2d44031f8c005f6f86364d7663858b6b5bdd14 Mon Sep 17 00:00:00 2001 From: Simon Riggs Date: Thu, 5 Apr 2018 09:54:07 +0100 Subject: MERGE post-commit review Review comments from Andres Freund * Consolidate code into AfterTriggerGetTransitionTable() * Rename nodeMerge.c to execMerge.c * Rename nodeMerge.h to execMerge.h * Move MERGE handling in ExecInitModifyTable() into a execMerge.c ExecInitMerge() * Move mt_merge_subcommands flags into execMerge.h * Rename opt_and_condition to opt_merge_when_and_condition * Wordsmith various comments Author: Pavan Deolasee Reviewer: Simon Riggs --- src/backend/commands/trigger.c | 192 +++++---- src/backend/executor/Makefile | 4 +- src/backend/executor/README | 11 +- src/backend/executor/execMerge.c | 683 +++++++++++++++++++++++++++++++++ src/backend/executor/execPartition.c | 9 +- src/backend/executor/nodeMerge.c | 575 --------------------------- src/backend/executor/nodeModifyTable.c | 109 +----- src/backend/optimizer/plan/setrefs.c | 16 +- src/backend/parser/gram.y | 12 +- src/include/executor/execMerge.h | 31 ++ src/include/executor/nodeMerge.h | 22 -- src/include/executor/nodeModifyTable.h | 1 + 12 files changed, 862 insertions(+), 803 deletions(-) create mode 100644 src/backend/executor/execMerge.c delete mode 100644 src/backend/executor/nodeMerge.c create mode 100644 src/include/executor/execMerge.h delete mode 100644 src/include/executor/nodeMerge.h (limited to 'src') diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index e71f921fda1..a189356cada 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -96,6 +96,12 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata, FmgrInfo *finfo, Instrumentation *instr, MemoryContext per_tuple_context); +static Tuplestorestate *AfterTriggerGetTransitionTable(int event, + HeapTuple oldtup, + HeapTuple newtup, + TransitionCaptureState *transition_capture); +static void TransitionTableAddTuple(HeapTuple heaptup, Tuplestorestate *tuplestore, + TupleConversionMap *map); static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, int event, bool row_trigger, HeapTuple oldtup, HeapTuple newtup, @@ -3846,6 +3852,14 @@ struct AfterTriggersTableData bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ + + /* + * We maintain separate transaction tables for UPDATE/INSERT/DELETE since + * MERGE can run all three actions in a single statement. Note that UPDATE + * needs both old and new transition tables whereas INSERT needs only new + * and DELETE needs only old. + */ + /* "old" transition table for UPDATE, if any */ Tuplestorestate *old_upd_tuplestore; /* "new" transition table for UPDATE, if any */ @@ -5716,6 +5730,84 @@ AfterTriggerPendingOnRel(Oid relid) return false; } +/* + * Get the transition table for the given event and depending on whether we are + * processing the old or the new tuple. + */ +static Tuplestorestate * +AfterTriggerGetTransitionTable(int event, + HeapTuple oldtup, + HeapTuple newtup, + TransitionCaptureState *transition_capture) +{ + Tuplestorestate *tuplestore = NULL; + bool delete_old_table = transition_capture->tcs_delete_old_table; + bool update_old_table = transition_capture->tcs_update_old_table; + bool update_new_table = transition_capture->tcs_update_new_table; + bool insert_new_table = transition_capture->tcs_insert_new_table;; + + /* + * For INSERT events newtup should be non-NULL, for DELETE events + * oldtup should be non-NULL, whereas for UPDATE events normally both + * oldtup and newtup are non-NULL. But for UPDATE events fired for + * capturing transition tuples during UPDATE partition-key row + * movement, oldtup is NULL when the event is for a row being inserted, + * whereas newtup is NULL when the event is for a row being deleted. + */ + Assert(!(event == TRIGGER_EVENT_DELETE && delete_old_table && + oldtup == NULL)); + Assert(!(event == TRIGGER_EVENT_INSERT && insert_new_table && + newtup == NULL)); + + /* + * We're called either for the newtup or the oldtup, but not both at the + * same time. + */ + Assert((oldtup != NULL) ^ (newtup != NULL)); + + if (oldtup != NULL) + { + if (event == TRIGGER_EVENT_DELETE && delete_old_table) + tuplestore = transition_capture->tcs_private->old_del_tuplestore; + else if (event == TRIGGER_EVENT_UPDATE && update_old_table) + tuplestore = transition_capture->tcs_private->old_upd_tuplestore; + } + + if (newtup != NULL) + { + if (event == TRIGGER_EVENT_INSERT && insert_new_table) + tuplestore = transition_capture->tcs_private->new_ins_tuplestore; + else if (event == TRIGGER_EVENT_UPDATE && update_new_table) + tuplestore = transition_capture->tcs_private->new_upd_tuplestore; + } + + return tuplestore; +} + +/* + * Add the given heap tuple to the given tuplestore, applying the conversion + * map if necessary. + */ +static void +TransitionTableAddTuple(HeapTuple heaptup, Tuplestorestate *tuplestore, + TupleConversionMap *map) +{ + /* + * Nothing needs to be done if we don't have a tuplestore. + */ + if (tuplestore == NULL) + return; + + if (map != NULL) + { + HeapTuple converted = do_convert_tuple(heaptup, map); + + tuplestore_puttuple(tuplestore, converted); + pfree(converted); + } + else + tuplestore_puttuple(tuplestore, heaptup); +} /* ---------- * AfterTriggerSaveEvent() @@ -5777,95 +5869,37 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, { HeapTuple original_insert_tuple = transition_capture->tcs_original_insert_tuple; TupleConversionMap *map = transition_capture->tcs_map; - bool delete_old_table = transition_capture->tcs_delete_old_table; - bool update_old_table = transition_capture->tcs_update_old_table; - bool update_new_table = transition_capture->tcs_update_new_table; - bool insert_new_table = transition_capture->tcs_insert_new_table;; /* - * For INSERT events newtup should be non-NULL, for DELETE events - * oldtup should be non-NULL, whereas for UPDATE events normally both - * oldtup and newtup are non-NULL. But for UPDATE events fired for - * capturing transition tuples during UPDATE partition-key row - * movement, oldtup is NULL when the event is for a row being inserted, - * whereas newtup is NULL when the event is for a row being deleted. + * Capture the old tuple in the appropriate transition table based on + * the event. */ - Assert(!(event == TRIGGER_EVENT_DELETE && delete_old_table && - oldtup == NULL)); - Assert(!(event == TRIGGER_EVENT_INSERT && insert_new_table && - newtup == NULL)); - - if (oldtup != NULL && - (event == TRIGGER_EVENT_DELETE && delete_old_table)) + if (oldtup != NULL) { - Tuplestorestate *old_tuplestore; - - old_tuplestore = transition_capture->tcs_private->old_del_tuplestore; - - if (map != NULL) - { - HeapTuple converted = do_convert_tuple(oldtup, map); - - tuplestore_puttuple(old_tuplestore, converted); - pfree(converted); - } - else - tuplestore_puttuple(old_tuplestore, oldtup); + Tuplestorestate *tuplestore = + AfterTriggerGetTransitionTable(event, + oldtup, + NULL, + transition_capture); + TransitionTableAddTuple(oldtup, tuplestore, map); } - if (oldtup != NULL && - (event == TRIGGER_EVENT_UPDATE && update_old_table)) - { - Tuplestorestate *old_tuplestore; - - old_tuplestore = transition_capture->tcs_private->old_upd_tuplestore; - - if (map != NULL) - { - HeapTuple converted = do_convert_tuple(oldtup, map); - tuplestore_puttuple(old_tuplestore, converted); - pfree(converted); - } - else - tuplestore_puttuple(old_tuplestore, oldtup); - } - if (newtup != NULL && - (event == TRIGGER_EVENT_INSERT && insert_new_table)) - { - Tuplestorestate *new_tuplestore; - - new_tuplestore = transition_capture->tcs_private->new_ins_tuplestore; - - if (original_insert_tuple != NULL) - tuplestore_puttuple(new_tuplestore, original_insert_tuple); - else if (map != NULL) - { - HeapTuple converted = do_convert_tuple(newtup, map); - - tuplestore_puttuple(new_tuplestore, converted); - pfree(converted); - } - else - tuplestore_puttuple(new_tuplestore, newtup); - } - if (newtup != NULL && - (event == TRIGGER_EVENT_UPDATE && update_new_table)) + /* + * Capture the new tuple in the appropriate transition table based on + * the event. + */ + if (newtup != NULL) { - Tuplestorestate *new_tuplestore; - - new_tuplestore = transition_capture->tcs_private->new_upd_tuplestore; + Tuplestorestate *tuplestore = + AfterTriggerGetTransitionTable(event, + NULL, + newtup, + transition_capture); if (original_insert_tuple != NULL) - tuplestore_puttuple(new_tuplestore, original_insert_tuple); - else if (map != NULL) - { - HeapTuple converted = do_convert_tuple(newtup, map); - - tuplestore_puttuple(new_tuplestore, converted); - pfree(converted); - } + tuplestore_puttuple(tuplestore, original_insert_tuple); else - tuplestore_puttuple(new_tuplestore, newtup); + TransitionTableAddTuple(newtup, tuplestore, map); } /* diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index 68675f97966..76d87eea49c 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \ execGrouping.o execIndexing.o execJunk.o \ - execMain.o execParallel.o execPartition.o execProcnode.o \ + execMain.o execMerge.o execParallel.o execPartition.o execProcnode.o \ execReplication.o execScan.o execSRF.o execTuples.o \ execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \ nodeBitmapAnd.o nodeBitmapOr.o \ @@ -22,7 +22,7 @@ OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \ nodeCustom.o nodeFunctionscan.o nodeGather.o \ nodeHash.o nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \ nodeLimit.o nodeLockRows.o nodeGatherMerge.o \ - nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeMerge.o nodeModifyTable.o \ + nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \ nodeNestloop.o nodeProjectSet.o nodeRecursiveunion.o nodeResult.o \ nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \ nodeValuesscan.o \ diff --git a/src/backend/executor/README b/src/backend/executor/README index 05769772b77..7882ce44e78 100644 --- a/src/backend/executor/README +++ b/src/backend/executor/README @@ -39,13 +39,14 @@ ModifyTable node visits each of those rows and marks the row deleted. MERGE runs one generic plan that returns candidate target rows. Each row consists of a super-row that contains all the columns needed by any of the -individual actions, plus a CTID and a TABLEOID junk columns. The CTID column is +individual actions, plus CTID and TABLEOID junk columns. The CTID column is required to know if a matching target row was found or not and the TABLEOID column is needed to find the underlying target partition, in case when the -target table is a partition table. If the CTID column is set we attempt to -activate WHEN MATCHED actions, or if it is NULL then we will attempt to -activate WHEN NOT MATCHED actions. Once we know which action is activated we -form the final result row and apply only those changes. +target table is a partition table. When a matching target tuple is found, the +CTID column identifies the matching target tuple and we attempt to activate +WHEN MATCHED actions. If a matching tuple is not found, then CTID column is +NULL and we attempt to activate WHEN NOT MATCHED actions. Once we know which +action is activated we form the final result row and apply only those changes. XXX a great deal more documentation needs to be written here... diff --git a/src/backend/executor/execMerge.c b/src/backend/executor/execMerge.c new file mode 100644 index 00000000000..471f64361d3 --- /dev/null +++ b/src/backend/executor/execMerge.c @@ -0,0 +1,683 @@ +/*------------------------------------------------------------------------- + * + * execMerge.c + * routines to handle Merge nodes relating to the MERGE command + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/executor/execMerge.c + * + *------------------------------------------------------------------------- + */ + + +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/xact.h" +#include "commands/trigger.h" +#include "executor/execPartition.h" +#include "executor/executor.h" +#include "executor/nodeModifyTable.h" +#include "executor/execMerge.h" +#include "miscadmin.h" +#include "nodes/nodeFuncs.h" +#include "storage/bufmgr.h" +#include "storage/lmgr.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/tqual.h" + +static void ExecMergeNotMatched(ModifyTableState *mtstate, EState *estate, + TupleTableSlot *slot); +static bool ExecMergeMatched(ModifyTableState *mtstate, EState *estate, + TupleTableSlot *slot, JunkFilter *junkfilter, + ItemPointer tupleid); +/* + * Perform MERGE. + */ +void +ExecMerge(ModifyTableState *mtstate, EState *estate, TupleTableSlot *slot, + JunkFilter *junkfilter, ResultRelInfo *resultRelInfo) +{ + ExprContext *econtext = mtstate->ps.ps_ExprContext; + ItemPointer tupleid; + ItemPointerData tuple_ctid; + bool matched = false; + char relkind; + Datum datum; + bool isNull; + + relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; + Assert(relkind == RELKIND_RELATION || + relkind == RELKIND_PARTITIONED_TABLE); + + /* + * Reset per-tuple memory context to free any expression evaluation + * storage allocated in the previous cycle. + */ + ResetExprContext(econtext); + + /* + * We run a JOIN between the target relation and the source relation to + * find a set of candidate source rows that has matching row in the target + * table and a set of candidate source rows that does not have matching + * row in the target table. If the join returns us a tuple with target + * relation's tid set, that implies that the join found a matching row for + * the given source tuple. This case triggers the WHEN MATCHED clause of + * the MERGE. Whereas a NULL in the target relation's ctid column + * indicates a NOT MATCHED case. + */ + datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, &isNull); + + if (!isNull) + { + matched = true; + tupleid = (ItemPointer) DatumGetPointer(datum); + tuple_ctid = *tupleid; /* be sure we don't free ctid!! */ + tupleid = &tuple_ctid; + } + else + { + matched = false; + tupleid = NULL; /* we don't need it for INSERT actions */ + } + + /* + * If we are dealing with a WHEN MATCHED case, we execute the first action + * for which the additional WHEN MATCHED AND quals pass. If an action + * without quals is found, that action is executed. + * + * Similarly, if we are dealing with WHEN NOT MATCHED case, we look at the + * given WHEN NOT MATCHED actions in sequence until one passes. + * + * Things get interesting in case of concurrent update/delete of the + * target tuple. Such concurrent update/delete is detected while we are + * executing a WHEN MATCHED action. + * + * A concurrent update can: + * + * 1. modify the target tuple so that it no longer satisfies the + * additional quals attached to the current WHEN MATCHED action OR + * + * In this case, we are still dealing with a WHEN MATCHED case, but + * we should recheck the list of WHEN MATCHED actions and choose the first + * one that satisfies the new target tuple. + * + * 2. modify the target tuple so that the join quals no longer pass and + * hence the source tuple no longer has a match. + * + * In the second case, the source tuple no longer matches the target tuple, + * so we now instead find a qualifying WHEN NOT MATCHED action to execute. + * + * A concurrent delete, changes a WHEN MATCHED case to WHEN NOT MATCHED. + * + * ExecMergeMatched takes care of following the update chain and + * re-finding the qualifying WHEN MATCHED action, as long as the updated + * target tuple still satisfies the join quals i.e. it still remains a + * WHEN MATCHED case. If the tuple gets deleted or the join quals fail, it + * returns and we try ExecMergeNotMatched. Given that ExecMergeMatched + * always make progress by following the update chain and we never switch + * from ExecMergeNotMatched to ExecMergeMatched, there is no risk of a + * livelock. + */ + if (matched) + matched = ExecMergeMatched(mtstate, estate, slot, junkfilter, tupleid); + + /* + * Either we were dealing with a NOT MATCHED tuple or ExecMergeNotMatched() + * returned "false", indicating the previously MATCHED tuple is no longer a + * matching tuple. + */ + if (!matched) + ExecMergeNotMatched(mtstate, estate, slot); +} + +/* + * Check and execute the first qualifying MATCHED action. The current target + * tuple is identified by tupleid. + * + * We start from the first WHEN MATCHED action and check if the WHEN AND quals + * pass, if any. If the WHEN AND quals for the first action do not pass, we + * check the second, then the third and so on. If we reach to the end, no + * action is taken and we return true, indicating that no further action is + * required for this tuple. + * + * If we do find a qualifying action, then we attempt to execute the action. + * + * If the tuple is concurrently updated, EvalPlanQual is run with the updated + * tuple to recheck the join quals. Note that the additional quals associated + * with individual actions are evaluated separately by the MERGE code, while + * EvalPlanQual checks for the join quals. If EvalPlanQual tells us that the + * updated tuple still passes the join quals, then we restart from the first + * action to look for a qualifying action. Otherwise, we return false meaning + * that a NOT MATCHED action must now be executed for the current source tuple. + */ +static bool +ExecMergeMatched(ModifyTableState *mtstate, EState *estate, + TupleTableSlot *slot, JunkFilter *junkfilter, + ItemPointer tupleid) +{ + ExprContext *econtext = mtstate->ps.ps_ExprContext; + bool isNull; + List *mergeMatchedActionStates = NIL; + HeapUpdateFailureData hufd; + bool tuple_updated, + tuple_deleted; + Buffer buffer; + HeapTupleData tuple; + EPQState *epqstate = &mtstate->mt_epqstate; + ResultRelInfo *saved_resultRelInfo; + ResultRelInfo *resultRelInfo = estate->es_result_relation_info; + ListCell *l; + TupleTableSlot *saved_slot = slot; + + if (mtstate->mt_partition_tuple_routing) + { + Datum datum; + Oid tableoid = InvalidOid; + int leaf_part_index; + PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing; + + /* + * In case of partitioned table, we fetch the tableoid while performing + * MATCHED MERGE action. + */ + datum = ExecGetJunkAttribute(slot, junkfilter->jf_otherJunkAttNo, + &isNull); + Assert(!isNull); + tableoid = DatumGetObjectId(datum); + + /* + * If we're dealing with a MATCHED tuple, then tableoid must have been + * set correctly. In case of partitioned table, we must now fetch the + * correct result relation corresponding to the child table emitting + * the matching target row. For normal table, there is just one result + * relation and it must be the one emitting the matching row. + */ + leaf_part_index = ExecFindPartitionByOid(proute, tableoid); + + resultRelInfo = proute->partitions[leaf_part_index]; + if (resultRelInfo == NULL) + { + resultRelInfo = ExecInitPartitionInfo(mtstate, + mtstate->resultRelInfo, + proute, estate, leaf_part_index); + Assert(resultRelInfo != NULL); + } + } + + /* + * Save the current information and work with the correct result relation. + */ + saved_resultRelInfo = resultRelInfo; + estate->es_result_relation_info = resultRelInfo; + + /* + * And get the correct action lists. + */ + mergeMatchedActionStates = + resultRelInfo->ri_mergeState->matchedActionStates; + + /* + * If there are not WHEN MATCHED actions, we are done. + */ + if (mergeMatchedActionStates == NIL) + return true; + + /* + * Make tuple and any needed join variables available to ExecQual and + * ExecProject. The target's existing tuple is installed in the scantuple. + * Again, this target relation's slot is required only in the case of a + * MATCHED tuple and UPDATE/DELETE actions. + */ + if (mtstate->mt_partition_tuple_routing) + ExecSetSlotDescriptor(mtstate->mt_existing, + resultRelInfo->ri_RelationDesc->rd_att); + econtext->ecxt_scantuple = mtstate->mt_existing; + econtext->ecxt_innertuple = slot; + econtext->ecxt_outertuple = NULL; + +lmerge_matched:; + slot = saved_slot; + + /* + * UPDATE/DELETE is only invoked for matched rows. And we must have found + * the tupleid of the target row in that case. We fetch using SnapshotAny + * because we might get called again after EvalPlanQual returns us a new + * tuple. This tuple may not be visible to our MVCC snapshot. + */ + Assert(tupleid != NULL); + + tuple.t_self = *tupleid; + if (!heap_fetch(resultRelInfo->ri_RelationDesc, SnapshotAny, &tuple, + &buffer, true, NULL)) + elog(ERROR, "Failed to fetch the target tuple"); + + /* Store target's existing tuple in the state's dedicated slot */ + ExecStoreTuple(&tuple, mtstate->mt_existing, buffer, false); + + foreach(l, mergeMatchedActionStates) + { + MergeActionState *action = (MergeActionState *) lfirst(l); + + /* + * Test condition, if any + * + * In the absence of a condition we perform the action unconditionally + * (no need to check separately since ExecQual() will return true if + * there are no conditions to evaluate). + */ + if (!ExecQual(action->whenqual, econtext)) + continue; + + /* + * Check if the existing target tuple meet the USING checks of + * UPDATE/DELETE RLS policies. If those checks fail, we throw an + * error. + * + * The WITH CHECK quals are applied in ExecUpdate() and hence we need + * not do anything special to handle them. + * + * NOTE: We must do this after WHEN quals are evaluated so that we + * check policies only when they matter. + */ + if (resultRelInfo->ri_WithCheckOptions) + { + ExecWithCheckOptions(action->commandType == CMD_UPDATE ? + WCO_RLS_MERGE_UPDATE_CHECK : WCO_RLS_MERGE_DELETE_CHECK, + resultRelInfo, + mtstate->mt_existing, + mtstate->ps.state); + } + + /* Perform stated action */ + switch (action->commandType) + { + case CMD_UPDATE: + + /* + * We set up the projection earlier, so all we do here is + * Project, no need for any other tasks prior to the + * ExecUpdate. + */ + if (mtstate->mt_partition_tuple_routing) + ExecSetSlotDescriptor(mtstate->mt_mergeproj, action->tupDesc); + ExecProject(action->proj); + + /* + * We don't call ExecFilterJunk() because the projected tuple + * using the UPDATE action's targetlist doesn't have a junk + * attribute. + */ + slot = ExecUpdate(mtstate, tupleid, NULL, + mtstate->mt_mergeproj, + slot, epqstate, estate, + &tuple_updated, &hufd, + action, mtstate->canSetTag); + break; + + case CMD_DELETE: + /* Nothing to Project for a DELETE action */ + slot = ExecDelete(mtstate, tupleid, NULL, + slot, epqstate, estate, + &tuple_deleted, false, &hufd, action, + mtstate->canSetTag); + + break; + + default: + elog(ERROR, "unknown action in MERGE WHEN MATCHED clause"); + + } + + /* + * Check for any concurrent update/delete operation which may have + * prevented our update/delete. We also check for situations where we + * might be trying to update/delete the same tuple twice. + */ + if ((action->commandType == CMD_UPDATE && !tuple_updated) || + (action->commandType == CMD_DELETE && !tuple_deleted)) + + { + switch (hufd.result) + { + case HeapTupleMayBeUpdated: + break; + case HeapTupleInvisible: + + /* + * This state should never be reached since the underlying + * JOIN runs with a MVCC snapshot and EvalPlanQual runs + * with a dirty snapshot. So such a row should have never + * been returned for MERGE. + */ + elog(ERROR, "unexpected invisible tuple"); + break; + + case HeapTupleSelfUpdated: + + /* + * SQLStandard disallows this for MERGE. + */ + if (TransactionIdIsCurrentTransactionId(hufd.xmax)) + ereport(ERROR, + (errcode(ERRCODE_CARDINALITY_VIOLATION), + errmsg("MERGE command cannot affect row a second time"), + errhint("Ensure that not more than one source row matches any one target row"))); + /* This shouldn't happen */ + elog(ERROR, "attempted to update or delete invisible tuple"); + break; + + case HeapTupleUpdated: + + /* + * The target tuple was concurrently updated/deleted by + * some other transaction. + * + * If the current tuple is that last tuple in the update + * chain, then we know that the tuple was concurrently + * deleted. Just return and let the caller try NOT MATCHED + * actions. + * + * If the current tuple was concurrently updated, then we + * must run the EvalPlanQual() with the new version of the + * tuple. If EvalPlanQual() does not return a tuple then + * we switch to the NOT MATCHED list of actions. + * If it does return a tuple and the join qual is + * still satisfied, then we just need to recheck the + * MATCHED actions, starting from the top, and execute the + * first qualifying action. + */ + if (!ItemPointerEquals(tupleid, &hufd.ctid)) + { + TupleTableSlot *epqslot; + + /* + * Since we generate a JOIN query with a target table + * RTE different than the result relation RTE, we must + * pass in the RTI of the relation used in the join + * query and not the one from result relation. + */ + Assert(resultRelInfo->ri_mergeTargetRTI > 0); + epqslot = EvalPlanQual(estate, + epqstate, + resultRelInfo->ri_RelationDesc, + GetEPQRangeTableIndex(resultRelInfo), + LockTupleExclusive, + &hufd.ctid, + hufd.xmax); + + if (!TupIsNull(epqslot)) + { + (void) ExecGetJunkAttribute(epqslot, + resultRelInfo->ri_junkFilter->jf_junkAttNo, + &isNull); + + /* + * A non-NULL ctid means that we are still dealing + * with MATCHED case. But we must retry from the + * start with the updated tuple to ensure that the + * first qualifying WHEN MATCHED action is + * executed. + * + * We don't use the new slot returned by + * EvalPlanQual because we anyways re-install the + * new target tuple in econtext->ecxt_scantuple + * before re-evaluating WHEN AND conditions and + * re-projecting the update targetlists. The + * source side tuple does not change and hence we + * can safely continue to use the old slot. + */ + if (!isNull) + { + /* + * Must update *tupleid to the TID of the + * newer tuple found in the update chain. + */ + *tupleid = hufd.ctid; + ReleaseBuffer(buffer); + goto lmerge_matched; + } + } + } + + /* + * Tell the caller about the updated TID, restore the + * state back and return. + */ + *tupleid = hufd.ctid; + estate->es_result_relation_info = saved_resultRelInfo; + ReleaseBuffer(buffer); + return false; + + default: + break; + + } + } + + if (action->commandType == CMD_UPDATE && tuple_updated) + InstrCountFiltered2(&mtstate->ps, 1); + if (action->commandType == CMD_DELETE && tuple_deleted) + InstrCountFiltered3(&mtstate->ps, 1); + + /* + * We've activated one of the WHEN clauses, so we don't search + * further. This is required behaviour, not an optimization. + */ + estate->es_result_relation_info = saved_resultRelInfo; + break; + } + + ReleaseBuffer(buffer); + + /* + * Successfully executed an action or no qualifying action was found. + */ + return true; +} + +/* + * Execute the first qualifying NOT MATCHED action. + */ +static void +ExecMergeNotMatched(ModifyTableState *mtstate, EState *estate, + TupleTableSlot *slot) +{ + PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing; + ExprContext *econtext = mtstate->ps.ps_ExprContext; + List *mergeNotMatchedActionStates = NIL; + ResultRelInfo *resultRelInfo; + ListCell *l; + TupleTableSlot *myslot; + + /* + * We are dealing with NOT MATCHED tuple. Since for MERGE, the partition + * tree is not expanded for the result relation, we continue to work with + * the currently active result relation, which corresponds to the root + * of the partition tree. + */ + resultRelInfo = mtstate->resultRelInfo; + + /* + * For INSERT actions, root relation's merge action is OK since the + * INSERT's targetlist and the WHEN conditions can only refer to the + * source relation and hence it does not matter which result relation we + * work with. + */ + mergeNotMatchedActionStates = + resultRelInfo->ri_mergeState->notMatchedActionStates; + + /* + * Make source tuple available to ExecQual and ExecProject. We don't need + * the target tuple since the WHEN quals and the targetlist can't refer to + * the target columns. + */ + econtext->ecxt_scantuple = NULL; + econtext->ecxt_innertuple = slot; + econtext->ecxt_outertuple = NULL; + + foreach(l, mergeNotMatchedActionStates) + { + MergeActionState *action = (MergeActionState *) lfirst(l); + + /* + * Test condition, if any + * + * In the absence of a condition we perform the action unconditionally + * (no need to check separately since ExecQual() will return true if + * there are no conditions to evaluate). + */ + if (!ExecQual(action->whenqual, econtext)) + continue; + + /* Perform stated action */ + switch (action->commandType) + { + case CMD_INSERT: + + /* + * We set up the projection earlier, so all we do here is + * Project, no need for any other tasks prior to the + * ExecInsert. + */ + if (mtstate->mt_partition_tuple_routing) + ExecSetSlotDescriptor(mtstate->mt_mergeproj, action->tupDesc); + ExecProject(action->proj); + + /* + * ExecPrepareTupleRouting may modify the passed-in slot. Hence + * pass a local reference so that action->slot is not modified. + */ + myslot = mtstate->mt_mergeproj; + + /* Prepare for tuple routing if needed. */ + if (proute) + myslot = ExecPrepareTupleRouting(mtstate, estate, proute, + resultRelInfo, myslot); + slot = ExecInsert(mtstate, myslot, slot, + estate, action, + mtstate->canSetTag); + /* Revert ExecPrepareTupleRouting's state change. */ + if (proute) + estate->es_result_relation_info = resultRelInfo; + InstrCountFiltered1(&mtstate->ps, 1); + break; + case CMD_NOTHING: + /* Do Nothing */ + break; + default: + elog(ERROR, "unknown action in MERGE WHEN NOT MATCHED clause"); + } + + break; + } +} + +void +ExecInitMerge(ModifyTableState *mtstate, EState *estate, + ResultRelInfo *resultRelInfo) +{ + ListCell *l; + ExprContext *econtext; + List *mergeMatchedActionStates = NIL; + List *mergeNotMatchedActionStates = NIL; + TupleDesc relationDesc = resultRelInfo->ri_RelationDesc->rd_att; + ModifyTable *node = (ModifyTable *) mtstate->ps.plan; + + if (node->mergeActionList == NIL) + return; + + mtstate->mt_merge_subcommands = 0; + + if (mtstate->ps.ps_ExprContext == NULL) + ExecAssignExprContext(estate, &mtstate->ps); + + econtext = mtstate->ps.ps_ExprContext; + + /* initialize slot for the existing tuple */ + Assert(mtstate->mt_existing == NULL); + mtstate->mt_existing = + ExecInitExtraTupleSlot(mtstate->ps.state, + mtstate->mt_partition_tuple_routing ? + NULL : relationDesc); + + /* initialize slot for merge actions */ + Assert(mtstate->mt_mergeproj == NULL); + mtstate->mt_mergeproj = + ExecInitExtraTupleSlot(mtstate->ps.state, + mtstate->mt_partition_tuple_routing ? + NULL : relationDesc); + + /* + * Create a MergeActionState for each action on the mergeActionList + * and add it to either a list of matched actions or not-matched + * actions. + */ + foreach(l, node->mergeActionList) + { + MergeAction *action = (MergeAction *) lfirst(l); + MergeActionState *action_state = makeNode(MergeActionState); + TupleDesc tupDesc; + + action_state->matched = action->matched; + action_state->commandType = action->commandType; + action_state->whenqual = ExecInitQual((List *) action->qual, + &mtstate->ps); + + /* create target slot for this action's projection */ + tupDesc = ExecTypeFromTL((List *) action->targetList, + resultRelInfo->ri_RelationDesc->rd_rel->relhasoids); + action_state->tupDesc = tupDesc; + + /* build action projection state */ + action_state->proj = + ExecBuildProjectionInfo(action->targetList, econtext, + mtstate->mt_mergeproj, &mtstate->ps, + resultRelInfo->ri_RelationDesc->rd_att); + + /* + * We create two lists - one for WHEN MATCHED actions and one + * for WHEN NOT MATCHED actions - and stick the + * MergeActionState into the appropriate list. + */ + if (action_state->matched) + mergeMatchedActionStates = + lappend(mergeMatchedActionStates, action_state); + else + mergeNotMatchedActionStates = + lappend(mergeNotMatchedActionStates, action_state); + + switch (action->commandType) + { + case CMD_INSERT: + ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, + action->targetList); + mtstate->mt_merge_subcommands |= MERGE_INSERT; + break; + case CMD_UPDATE: + ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, + action->targetList); + mtstate->mt_merge_subcommands |= MERGE_UPDATE; + break; + case CMD_DELETE: + mtstate->mt_merge_subcommands |= MERGE_DELETE; + break; + case CMD_NOTHING: + break; + default: + elog(ERROR, "unknown operation"); + break; + } + + resultRelInfo->ri_mergeState->matchedActionStates = + mergeMatchedActionStates; + resultRelInfo->ri_mergeState->notMatchedActionStates = + mergeNotMatchedActionStates; + } +} diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index a6a7885abd1..007f00569ce 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -313,6 +313,10 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd, /* * Given OID of the partition leaf, return the index of the leaf in the * partition hierarchy. + * + * NB: This is an O(N) operation. Unfortunately, there are many other problem + * areas with more than a handful partitions, so we don't try to optimise this + * code right now. */ int ExecFindPartitionByOid(PartitionTupleRouting *proute, Oid partoid) @@ -325,7 +329,10 @@ ExecFindPartitionByOid(PartitionTupleRouting *proute, Oid partoid) break; } - Assert(i < proute->num_partitions); + if (i >= proute->num_partitions) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("no partition found for OID %u", partoid))); return i; } diff --git a/src/backend/executor/nodeMerge.c b/src/backend/executor/nodeMerge.c deleted file mode 100644 index 0e0d0795d4d..00000000000 --- a/src/backend/executor/nodeMerge.c +++ /dev/null @@ -1,575 +0,0 @@ -/*------------------------------------------------------------------------- - * - * nodeMerge.c - * routines to handle Merge nodes relating to the MERGE command - * - * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION - * src/backend/executor/nodeMerge.c - * - *------------------------------------------------------------------------- - */ - - -#include "postgres.h" - -#include "access/htup_details.h" -#include "access/xact.h" -#include "commands/trigger.h" -#include "executor/execPartition.h" -#include "executor/executor.h" -#include "executor/nodeModifyTable.h" -#include "executor/nodeMerge.h" -#include "miscadmin.h" -#include "nodes/nodeFuncs.h" -#include "storage/bufmgr.h" -#include "storage/lmgr.h" -#include "utils/builtins.h" -#include "utils/memutils.h" -#include "utils/rel.h" -#include "utils/tqual.h" - - -/* - * Check and execute the first qualifying MATCHED action. The current target - * tuple is identified by tupleid. - * - * We start from the first WHEN MATCHED action and check if the WHEN AND quals - * pass, if any. If the WHEN AND quals for the first action do not pass, we - * check the second, then the third and so on. If we reach to the end, no - * action is taken and we return true, indicating that no further action is - * required for this tuple. - * - * If we do find a qualifying action, then we attempt to execute the action. - * - * If the tuple is concurrently updated, EvalPlanQual is run with the updated - * tuple to recheck the join quals. Note that the additional quals associated - * with individual actions are evaluated separately by the MERGE code, while - * EvalPlanQual checks for the join quals. If EvalPlanQual tells us that the - * updated tuple still passes the join quals, then we restart from the first - * action to look for a qualifying action. Otherwise, we return false meaning - * that a NOT MATCHED action must now be executed for the current source tuple. - */ -static bool -ExecMergeMatched(ModifyTableState *mtstate, EState *estate, - TupleTableSlot *slot, JunkFilter *junkfilter, - ItemPointer tupleid) -{ - ExprContext *econtext = mtstate->ps.ps_ExprContext; - bool isNull; - List *mergeMatchedActionStates = NIL; - HeapUpdateFailureData hufd; - bool tuple_updated, - tuple_deleted; - Buffer buffer; - HeapTupleData tuple; - EPQState *epqstate = &mtstate->mt_epqstate; - ResultRelInfo *saved_resultRelInfo; - ResultRelInfo *resultRelInfo = estate->es_result_relation_info; - ListCell *l; - TupleTableSlot *saved_slot = slot; - - if (mtstate->mt_partition_tuple_routing) - { - Datum datum; - Oid tableoid = InvalidOid; - int leaf_part_index; - PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing; - - /* - * In case of partitioned table, we fetch the tableoid while performing - * MATCHED MERGE action. - */ - datum = ExecGetJunkAttribute(slot, junkfilter->jf_otherJunkAttNo, - &isNull); - Assert(!isNull); - tableoid = DatumGetObjectId(datum); - - /* - * If we're dealing with a MATCHED tuple, then tableoid must have been - * set correctly. In case of partitioned table, we must now fetch the - * correct result relation corresponding to the child table emitting - * the matching target row. For normal table, there is just one result - * relation and it must be the one emitting the matching row. - */ - leaf_part_index = ExecFindPartitionByOid(proute, tableoid); - - resultRelInfo = proute->partitions[leaf_part_index]; - if (resultRelInfo == NULL) - { - resultRelInfo = ExecInitPartitionInfo(mtstate, - mtstate->resultRelInfo, - proute, estate, leaf_part_index); - Assert(resultRelInfo != NULL); - } - } - - /* - * Save the current information and work with the correct result relation. - */ - saved_resultRelInfo = resultRelInfo; - estate->es_result_relation_info = resultRelInfo; - - /* - * And get the correct action lists. - */ - mergeMatchedActionStates = - resultRelInfo->ri_mergeState->matchedActionStates; - - /* - * If there are not WHEN MATCHED actions, we are done. - */ - if (mergeMatchedActionStates == NIL) - return true; - - /* - * Make tuple and any needed join variables available to ExecQual and - * ExecProject. The target's existing tuple is installed in the scantuple. - * Again, this target relation's slot is required only in the case of a - * MATCHED tuple and UPDATE/DELETE actions. - */ - if (mtstate->mt_partition_tuple_routing) - ExecSetSlotDescriptor(mtstate->mt_existing, - resultRelInfo->ri_RelationDesc->rd_att); - econtext->ecxt_scantuple = mtstate->mt_existing; - econtext->ecxt_innertuple = slot; - econtext->ecxt_outertuple = NULL; - -lmerge_matched:; - slot = saved_slot; - - /* - * UPDATE/DELETE is only invoked for matched rows. And we must have found - * the tupleid of the target row in that case. We fetch using SnapshotAny - * because we might get called again after EvalPlanQual returns us a new - * tuple. This tuple may not be visible to our MVCC snapshot. - */ - Assert(tupleid != NULL); - - tuple.t_self = *tupleid; - if (!heap_fetch(resultRelInfo->ri_RelationDesc, SnapshotAny, &tuple, - &buffer, true, NULL)) - elog(ERROR, "Failed to fetch the target tuple"); - - /* Store target's existing tuple in the state's dedicated slot */ - ExecStoreTuple(&tuple, mtstate->mt_existing, buffer, false); - - foreach(l, mergeMatchedActionStates) - { - MergeActionState *action = (MergeActionState *) lfirst(l); - - /* - * Test condition, if any - * - * In the absence of a condition we perform the action unconditionally - * (no need to check separately since ExecQual() will return true if - * there are no conditions to evaluate). - */ - if (!ExecQual(action->whenqual, econtext)) - continue; - - /* - * Check if the existing target tuple meet the USING checks of - * UPDATE/DELETE RLS policies. If those checks fail, we throw an - * error. - * - * The WITH CHECK quals are applied in ExecUpdate() and hence we need - * not do anything special to handle them. - * - * NOTE: We must do this after WHEN quals are evaluated so that we - * check policies only when they matter. - */ - if (resultRelInfo->ri_WithCheckOptions) - { - ExecWithCheckOptions(action->commandType == CMD_UPDATE ? - WCO_RLS_MERGE_UPDATE_CHECK : WCO_RLS_MERGE_DELETE_CHECK, - resultRelInfo, - mtstate->mt_existing, - mtstate->ps.state); - } - - /* Perform stated action */ - switch (action->commandType) - { - case CMD_UPDATE: - - /* - * We set up the projection earlier, so all we do here is - * Project, no need for any other tasks prior to the - * ExecUpdate. - */ - if (mtstate->mt_partition_tuple_routing) - ExecSetSlotDescriptor(mtstate->mt_mergeproj, action->tupDesc); - ExecProject(action->proj); - - /* - * We don't call ExecFilterJunk() because the projected tuple - * using the UPDATE action's targetlist doesn't have a junk - * attribute. - */ - slot = ExecUpdate(mtstate, tupleid, NULL, - mtstate->mt_mergeproj, - slot, epqstate, estate, - &tuple_updated, &hufd, - action, mtstate->canSetTag); - break; - - case CMD_DELETE: - /* Nothing to Project for a DELETE action */ - slot = ExecDelete(mtstate, tupleid, NULL, - slot, epqstate, estate, - &tuple_deleted, false, &hufd, action, - mtstate->canSetTag); - - break; - - default: - elog(ERROR, "unknown action in MERGE WHEN MATCHED clause"); - - } - - /* - * Check for any concurrent update/delete operation which may have - * prevented our update/delete. We also check for situations where we - * might be trying to update/delete the same tuple twice. - */ - if ((action->commandType == CMD_UPDATE && !tuple_updated) || - (action->commandType == CMD_DELETE && !tuple_deleted)) - - { - switch (hufd.result) - { - case HeapTupleMayBeUpdated: - break; - case HeapTupleInvisible: - - /* - * This state should never be reached since the underlying - * JOIN runs with a MVCC snapshot and should only return - * rows visible to us. - */ - elog(ERROR, "unexpected invisible tuple"); - break; - - case HeapTupleSelfUpdated: - - /* - * SQLStandard disallows this for MERGE. - */ - if (TransactionIdIsCurrentTransactionId(hufd.xmax)) - ereport(ERROR, - (errcode(ERRCODE_CARDINALITY_VIOLATION), - errmsg("MERGE command cannot affect row a second time"), - errhint("Ensure that not more than one source row matches any one target row"))); - /* This shouldn't happen */ - elog(ERROR, "attempted to update or delete invisible tuple"); - break; - - case HeapTupleUpdated: - - /* - * The target tuple was concurrently updated/deleted by - * some other transaction. - * - * If the current tuple is that last tuple in the update - * chain, then we know that the tuple was concurrently - * deleted. Just return and let the caller try NOT MATCHED - * actions. - * - * If the current tuple was concurrently updated, then we - * must run the EvalPlanQual() with the new version of the - * tuple. If EvalPlanQual() does not return a tuple then - * we switch to the NOT MATCHED list of actions. - * If it does return a tuple and the join qual is - * still satisfied, then we just need to recheck the - * MATCHED actions, starting from the top, and execute the - * first qualifying action. - */ - if (!ItemPointerEquals(tupleid, &hufd.ctid)) - { - TupleTableSlot *epqslot; - - /* - * Since we generate a JOIN query with a target table - * RTE different than the result relation RTE, we must - * pass in the RTI of the relation used in the join - * query and not the one from result relation. - */ - Assert(resultRelInfo->ri_mergeTargetRTI > 0); - epqslot = EvalPlanQual(estate, - epqstate, - resultRelInfo->ri_RelationDesc, - GetEPQRangeTableIndex(resultRelInfo), - LockTupleExclusive, - &hufd.ctid, - hufd.xmax); - - if (!TupIsNull(epqslot)) - { - (void) ExecGetJunkAttribute(epqslot, - resultRelInfo->ri_junkFilter->jf_junkAttNo, - &isNull); - - /* - * A non-NULL ctid means that we are still dealing - * with MATCHED case. But we must retry from the - * start with the updated tuple to ensure that the - * first qualifying WHEN MATCHED action is - * executed. - * - * We don't use the new slot returned by - * EvalPlanQual because we anyways re-install the - * new target tuple in econtext->ecxt_scantuple - * before re-evaluating WHEN AND conditions and - * re-projecting the update targetlists. The - * source side tuple does not change and hence we - * can safely continue to use the old slot. - */ - if (!isNull) - { - /* - * Must update *tupleid to the TID of the - * newer tuple found in the update chain. - */ - *tupleid = hufd.ctid; - ReleaseBuffer(buffer); - goto lmerge_matched; - } - } - } - - /* - * Tell the caller about the updated TID, restore the - * state back and return. - */ - *tupleid = hufd.ctid; - estate->es_result_relation_info = saved_resultRelInfo; - ReleaseBuffer(buffer); - return false; - - default: - break; - - } - } - - if (action->commandType == CMD_UPDATE && tuple_updated) - InstrCountFiltered2(&mtstate->ps, 1); - if (action->commandType == CMD_DELETE && tuple_deleted) - InstrCountFiltered3(&mtstate->ps, 1); - - /* - * We've activated one of the WHEN clauses, so we don't search - * further. This is required behaviour, not an optimization. - */ - estate->es_result_relation_info = saved_resultRelInfo; - break; - } - - ReleaseBuffer(buffer); - - /* - * Successfully executed an action or no qualifying action was found. - */ - return true; -} - -/* - * Execute the first qualifying NOT MATCHED action. - */ -static void -ExecMergeNotMatched(ModifyTableState *mtstate, EState *estate, - TupleTableSlot *slot) -{ - PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing; - ExprContext *econtext = mtstate->ps.ps_ExprContext; - List *mergeNotMatchedActionStates = NIL; - ResultRelInfo *resultRelInfo; - ListCell *l; - TupleTableSlot *myslot; - - /* - * We are dealing with NOT MATCHED tuple. Since for MERGE, partition tree - * is not expanded for the result relation, we continue to work with the - * currently active result relation, which should be of the root of the - * partition tree. - */ - resultRelInfo = mtstate->resultRelInfo; - - /* - * For INSERT actions, root relation's merge action is OK since the - * INSERT's targetlist and the WHEN conditions can only refer to the - * source relation and hence it does not matter which result relation we - * work with. - */ - mergeNotMatchedActionStates = - resultRelInfo->ri_mergeState->notMatchedActionStates; - - /* - * Make source tuple available to ExecQual and ExecProject. We don't need - * the target tuple since the WHEN quals and the targetlist can't refer to - * the target columns. - */ - econtext->ecxt_scantuple = NULL; - econtext->ecxt_innertuple = slot; - econtext->ecxt_outertuple = NULL; - - foreach(l, mergeNotMatchedActionStates) - { - MergeActionState *action = (MergeActionState *) lfirst(l); - - /* - * Test condition, if any - * - * In the absence of a condition we perform the action unconditionally - * (no need to check separately since ExecQual() will return true if - * there are no conditions to evaluate). - */ - if (!ExecQual(action->whenqual, econtext)) - continue; - - /* Perform stated action */ - switch (action->commandType) - { - case CMD_INSERT: - - /* - * We set up the projection earlier, so all we do here is - * Project, no need for any other tasks prior to the - * ExecInsert. - */ - if (mtstate->mt_partition_tuple_routing) - ExecSetSlotDescriptor(mtstate->mt_mergeproj, action->tupDesc); - ExecProject(action->proj); - - /* - * ExecPrepareTupleRouting may modify the passed-in slot. Hence - * pass a local reference so that action->slot is not modified. - */ - myslot = mtstate->mt_mergeproj; - - /* Prepare for tuple routing if needed. */ - if (proute) - myslot = ExecPrepareTupleRouting(mtstate, estate, proute, - resultRelInfo, myslot); - slot = ExecInsert(mtstate, myslot, slot, - estate, action, - mtstate->canSetTag); - /* Revert ExecPrepareTupleRouting's state change. */ - if (proute) - estate->es_result_relation_info = resultRelInfo; - InstrCountFiltered1(&mtstate->ps, 1); - break; - case CMD_NOTHING: - /* Do Nothing */ - break; - default: - elog(ERROR, "unknown action in MERGE WHEN NOT MATCHED clause"); - } - - break; - } -} - -/* - * Perform MERGE. - */ -void -ExecMerge(ModifyTableState *mtstate, EState *estate, TupleTableSlot *slot, - JunkFilter *junkfilter, ResultRelInfo *resultRelInfo) -{ - ExprContext *econtext = mtstate->ps.ps_ExprContext; - ItemPointer tupleid; - ItemPointerData tuple_ctid; - bool matched = false; - char relkind; - Datum datum; - bool isNull; - - relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; - Assert(relkind == RELKIND_RELATION || - relkind == RELKIND_PARTITIONED_TABLE); - - /* - * Reset per-tuple memory context to free any expression evaluation - * storage allocated in the previous cycle. - */ - ResetExprContext(econtext); - - /* - * We run a JOIN between the target relation and the source relation to - * find a set of candidate source rows that has matching row in the target - * table and a set of candidate source rows that does not have matching - * row in the target table. If the join returns us a tuple with target - * relation's tid set, that implies that the join found a matching row for - * the given source tuple. This case triggers the WHEN MATCHED clause of - * the MERGE. Whereas a NULL in the target relation's ctid column - * indicates a NOT MATCHED case. - */ - datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, &isNull); - - if (!isNull) - { - matched = true; - tupleid = (ItemPointer) DatumGetPointer(datum); - tuple_ctid = *tupleid; /* be sure we don't free ctid!! */ - tupleid = &tuple_ctid; - } - else - { - matched = false; - tupleid = NULL; /* we don't need it for INSERT actions */ - } - - /* - * If we are dealing with a WHEN MATCHED case, we execute the first action - * for which the additional WHEN MATCHED AND quals pass. If an action - * without quals is found, that action is executed. - * - * Similarly, if we are dealing with WHEN NOT MATCHED case, we look at the - * given WHEN NOT MATCHED actions in sequence until one passes. - * - * Things get interesting in case of concurrent update/delete of the - * target tuple. Such concurrent update/delete is detected while we are - * executing a WHEN MATCHED action. - * - * A concurrent update can: - * - * 1. modify the target tuple so that it no longer satisfies the - * additional quals attached to the current WHEN MATCHED action OR - * - * In this case, we are still dealing with a WHEN MATCHED case, but - * we should recheck the list of WHEN MATCHED actions and choose the first - * one that satisfies the new target tuple. - * - * 2. modify the target tuple so that the join quals no longer pass and - * hence the source tuple no longer has a match. - * - * In the second case, the source tuple no longer matches the target tuple, - * so we now instead find a qualifying WHEN NOT MATCHED action to execute. - * - * A concurrent delete, changes a WHEN MATCHED case to WHEN NOT MATCHED. - * - * ExecMergeMatched takes care of following the update chain and - * re-finding the qualifying WHEN MATCHED action, as long as the updated - * target tuple still satisfies the join quals i.e. it still remains a - * WHEN MATCHED case. If the tuple gets deleted or the join quals fail, it - * returns and we try ExecMergeNotMatched. Given that ExecMergeMatched - * always make progress by following the update chain and we never switch - * from ExecMergeNotMatched to ExecMergeMatched, there is no risk of a - * livelock. - */ - if (matched) - matched = ExecMergeMatched(mtstate, estate, slot, junkfilter, tupleid); - - /* - * Either we were dealing with a NOT MATCHED tuple or ExecMergeNotMatched() - * returned "false", indicating the previously MATCHED tuple is no longer a - * matching tuple. - */ - if (!matched) - ExecMergeNotMatched(mtstate, estate, slot); -} diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index b03db64e8e1..0ebf37bd240 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -42,7 +42,7 @@ #include "commands/trigger.h" #include "executor/execPartition.h" #include "executor/executor.h" -#include "executor/nodeMerge.h" +#include "executor/execMerge.h" #include "executor/nodeModifyTable.h" #include "foreign/fdwapi.h" #include "miscadmin.h" @@ -69,11 +69,6 @@ static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate); static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node, int whichplan); -/* flags for mt_merge_subcommands */ -#define MERGE_INSERT 0x01 -#define MERGE_UPDATE 0x02 -#define MERGE_DELETE 0x04 - /* * Verify that the tuples to be produced by INSERT or UPDATE match the * target relation's rowtype @@ -86,7 +81,7 @@ static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node, * The plan output is represented by its targetlist, because that makes * handling the dropped-column case easier. */ -static void +void ExecCheckPlanOutput(Relation resultRel, List *targetList) { TupleDesc resultDesc = RelationGetDescr(resultRel); @@ -2660,104 +2655,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) } resultRelInfo = mtstate->resultRelInfo; - - if (node->mergeActionList) - { - ListCell *l; - ExprContext *econtext; - List *mergeMatchedActionStates = NIL; - List *mergeNotMatchedActionStates = NIL; - TupleDesc relationDesc = resultRelInfo->ri_RelationDesc->rd_att; - - mtstate->mt_merge_subcommands = 0; - - if (mtstate->ps.ps_ExprContext == NULL) - ExecAssignExprContext(estate, &mtstate->ps); - - econtext = mtstate->ps.ps_ExprContext; - - /* initialize slot for the existing tuple */ - Assert(mtstate->mt_existing == NULL); - mtstate->mt_existing = - ExecInitExtraTupleSlot(mtstate->ps.state, - mtstate->mt_partition_tuple_routing ? - NULL : relationDesc); - - /* initialize slot for merge actions */ - Assert(mtstate->mt_mergeproj == NULL); - mtstate->mt_mergeproj = - ExecInitExtraTupleSlot(mtstate->ps.state, - mtstate->mt_partition_tuple_routing ? - NULL : relationDesc); - - /* - * Create a MergeActionState for each action on the mergeActionList - * and add it to either a list of matched actions or not-matched - * actions. - */ - foreach(l, node->mergeActionList) - { - MergeAction *action = (MergeAction *) lfirst(l); - MergeActionState *action_state = makeNode(MergeActionState); - TupleDesc tupDesc; - - action_state->matched = action->matched; - action_state->commandType = action->commandType; - action_state->whenqual = ExecInitQual((List *) action->qual, - &mtstate->ps); - - /* create target slot for this action's projection */ - tupDesc = ExecTypeFromTL((List *) action->targetList, - resultRelInfo->ri_RelationDesc->rd_rel->relhasoids); - action_state->tupDesc = tupDesc; - - /* build action projection state */ - action_state->proj = - ExecBuildProjectionInfo(action->targetList, econtext, - mtstate->mt_mergeproj, &mtstate->ps, - resultRelInfo->ri_RelationDesc->rd_att); - - /* - * We create two lists - one for WHEN MATCHED actions and one - * for WHEN NOT MATCHED actions - and stick the - * MergeActionState into the appropriate list. - */ - if (action_state->matched) - mergeMatchedActionStates = - lappend(mergeMatchedActionStates, action_state); - else - mergeNotMatchedActionStates = - lappend(mergeNotMatchedActionStates, action_state); - - switch (action->commandType) - { - case CMD_INSERT: - ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, - action->targetList); - mtstate->mt_merge_subcommands |= MERGE_INSERT; - break; - case CMD_UPDATE: - ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, - action->targetList); - mtstate->mt_merge_subcommands |= MERGE_UPDATE; - break; - case CMD_DELETE: - mtstate->mt_merge_subcommands |= MERGE_DELETE; - break; - case CMD_NOTHING: - break; - default: - elog(ERROR, "unknown operation"); - break; - } - - resultRelInfo->ri_mergeState->matchedActionStates = - mergeMatchedActionStates; - resultRelInfo->ri_mergeState->notMatchedActionStates = - mergeNotMatchedActionStates; - - } - } + if (mtstate->operation == CMD_MERGE) + ExecInitMerge(mtstate, estate, resultRelInfo); /* select first subplan */ mtstate->mt_whichplan = 0; diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index cd540a0df5b..833a92f5387 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -852,14 +852,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) } /* - * The MERGE produces the target rows by performing a right - * join between the target relation and the source relation - * (which could be a plain relation or a subquery). The INSERT - * and UPDATE actions of the MERGE requires access to the - * columns from the source relation. We arrange things so that - * the source relation attributes are available as INNER_VAR - * and the target relation attributes are available from the - * scan tuple. + * The MERGE statement produces the target rows by performing a + * right join between the target relation and the source + * relation (which could be a plain relation or a subquery). + * The INSERT and UPDATE actions of the MERGE statement + * requires access to the columns from the source relation. We + * arrange things so that the source relation attributes are + * available as INNER_VAR and the target relation attributes + * are available from the scan tuple. */ if (splan->mergeActionList != NIL) { diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index b879358de16..1592b58bb47 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -585,7 +585,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type hash_partbound partbound_datum_list range_datum_list %type hash_partbound_elem -%type merge_when_clause opt_and_condition +%type merge_when_clause opt_merge_when_and_condition %type merge_when_list %type merge_update merge_delete merge_insert @@ -11129,7 +11129,7 @@ merge_when_list: ; merge_when_clause: - WHEN MATCHED opt_and_condition THEN merge_update + WHEN MATCHED opt_merge_when_and_condition THEN merge_update { MergeAction *m = makeNode(MergeAction); @@ -11140,7 +11140,7 @@ merge_when_clause: $$ = (Node *)m; } - | WHEN MATCHED opt_and_condition THEN merge_delete + | WHEN MATCHED opt_merge_when_and_condition THEN merge_delete { MergeAction *m = makeNode(MergeAction); @@ -11151,7 +11151,7 @@ merge_when_clause: $$ = (Node *)m; } - | WHEN NOT MATCHED opt_and_condition THEN merge_insert + | WHEN NOT MATCHED opt_merge_when_and_condition THEN merge_insert { MergeAction *m = makeNode(MergeAction); @@ -11162,7 +11162,7 @@ merge_when_clause: $$ = (Node *)m; } - | WHEN NOT MATCHED opt_and_condition THEN DO NOTHING + | WHEN NOT MATCHED opt_merge_when_and_condition THEN DO NOTHING { MergeAction *m = makeNode(MergeAction); @@ -11175,7 +11175,7 @@ merge_when_clause: } ; -opt_and_condition: +opt_merge_when_and_condition: AND a_expr { $$ = $2; } | { $$ = NULL; } ; diff --git a/src/include/executor/execMerge.h b/src/include/executor/execMerge.h new file mode 100644 index 00000000000..5ea8c4e50a8 --- /dev/null +++ b/src/include/executor/execMerge.h @@ -0,0 +1,31 @@ +/*------------------------------------------------------------------------- + * + * execMerge.h + * + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/executor/execMerge.h + * + *------------------------------------------------------------------------- + */ +#ifndef EXECMERGE_H +#define EXECMERGE_H + +#include "nodes/execnodes.h" + +/* flags for mt_merge_subcommands */ +#define MERGE_INSERT 0x01 +#define MERGE_UPDATE 0x02 +#define MERGE_DELETE 0x04 + +extern void ExecMerge(ModifyTableState *mtstate, EState *estate, + TupleTableSlot *slot, JunkFilter *junkfilter, + ResultRelInfo *resultRelInfo); + +extern void ExecInitMerge(ModifyTableState *mtstate, + EState *estate, + ResultRelInfo *resultRelInfo); + +#endif /* NODEMERGE_H */ diff --git a/src/include/executor/nodeMerge.h b/src/include/executor/nodeMerge.h deleted file mode 100644 index c222e9ee655..00000000000 --- a/src/include/executor/nodeMerge.h +++ /dev/null @@ -1,22 +0,0 @@ -/*------------------------------------------------------------------------- - * - * nodeMerge.h - * - * - * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * src/include/executor/nodeMerge.h - * - *------------------------------------------------------------------------- - */ -#ifndef NODEMERGE_H -#define NODEMERGE_H - -#include "nodes/execnodes.h" - -extern void -ExecMerge(ModifyTableState *mtstate, EState *estate, TupleTableSlot *slot, - JunkFilter *junkfilter, ResultRelInfo *resultRelInfo); - -#endif /* NODEMERGE_H */ diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h index 686cfa61710..94fd60c38cf 100644 --- a/src/include/executor/nodeModifyTable.h +++ b/src/include/executor/nodeModifyTable.h @@ -39,5 +39,6 @@ extern TupleTableSlot *ExecInsert(ModifyTableState *mtstate, EState *estate, MergeActionState *actionState, bool canSetTag); +extern void ExecCheckPlanOutput(Relation resultRel, List *targetList); #endif /* NODEMODIFYTABLE_H */ -- cgit v1.2.3