From 4b2d44031f8c005f6f86364d7663858b6b5bdd14 Mon Sep 17 00:00:00 2001 From: Simon Riggs Date: Thu, 5 Apr 2018 09:54:07 +0100 Subject: [PATCH] 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 +- .../executor/{nodeMerge.c => execMerge.c} | 302 ++++++++++++------ src/backend/executor/execPartition.c | 9 +- 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 + 11 files changed, 384 insertions(+), 325 deletions(-) rename src/backend/executor/{nodeMerge.c => execMerge.c} (82%) create mode 100644 src/include/executor/execMerge.h delete mode 100644 src/include/executor/nodeMerge.h diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index e71f921fda..a189356cad 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 68675f9796..76d87eea49 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 05769772b7..7882ce44e7 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/nodeMerge.c b/src/backend/executor/execMerge.c similarity index 82% rename from src/backend/executor/nodeMerge.c rename to src/backend/executor/execMerge.c index 0e0d0795d4..471f64361d 100644 --- a/src/backend/executor/nodeMerge.c +++ b/src/backend/executor/execMerge.c @@ -1,6 +1,6 @@ /*------------------------------------------------------------------------- * - * nodeMerge.c + * execMerge.c * routines to handle Merge nodes relating to the MERGE command * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * src/backend/executor/nodeMerge.c + * src/backend/executor/execMerge.c * *------------------------------------------------------------------------- */ @@ -22,7 +22,7 @@ #include "executor/execPartition.h" #include "executor/executor.h" #include "executor/nodeModifyTable.h" -#include "executor/nodeMerge.h" +#include "executor/execMerge.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "storage/bufmgr.h" @@ -32,6 +32,110 @@ #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 @@ -248,8 +352,9 @@ lmerge_matched:; /* * This state should never be reached since the underlying - * JOIN runs with a MVCC snapshot and should only return - * rows visible to us. + * 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; @@ -392,10 +497,10 @@ ExecMergeNotMatched(ModifyTableState *mtstate, EState *estate, 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. + * 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; @@ -474,102 +579,105 @@ ExecMergeNotMatched(ModifyTableState *mtstate, EState *estate, } } -/* - * Perform MERGE. - */ void -ExecMerge(ModifyTableState *mtstate, EState *estate, TupleTableSlot *slot, - JunkFilter *junkfilter, ResultRelInfo *resultRelInfo) +ExecInitMerge(ModifyTableState *mtstate, EState *estate, + ResultRelInfo *resultRelInfo) { - ExprContext *econtext = mtstate->ps.ps_ExprContext; - ItemPointer tupleid; - ItemPointerData tuple_ctid; - bool matched = false; - char relkind; - Datum datum; - bool isNull; + ListCell *l; + ExprContext *econtext; + List *mergeMatchedActionStates = NIL; + List *mergeNotMatchedActionStates = NIL; + TupleDesc relationDesc = resultRelInfo->ri_RelationDesc->rd_att; + ModifyTable *node = (ModifyTable *) mtstate->ps.plan; - relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; - Assert(relkind == RELKIND_RELATION || - relkind == RELKIND_PARTITIONED_TABLE); + if (node->mergeActionList == NIL) + return; - /* - * Reset per-tuple memory context to free any expression evaluation - * storage allocated in the previous cycle. - */ - ResetExprContext(econtext); + mtstate->mt_merge_subcommands = 0; - /* - * 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 (mtstate->ps.ps_ExprContext == NULL) + ExecAssignExprContext(estate, &mtstate->ps); - 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 */ - } + econtext = mtstate->ps.ps_ExprContext; - /* - * 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); + /* 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); /* - * Either we were dealing with a NOT MATCHED tuple or ExecMergeNotMatched() - * returned "false", indicating the previously MATCHED tuple is no longer a - * matching tuple. + * Create a MergeActionState for each action on the mergeActionList + * and add it to either a list of matched actions or not-matched + * actions. */ - if (!matched) - ExecMergeNotMatched(mtstate, estate, slot); + 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 a6a7885abd..007f00569c 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/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index b03db64e8e..0ebf37bd24 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 cd540a0df5..833a92f538 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 b879358de1..1592b58bb4 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 0000000000..5ea8c4e50a --- /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 c222e9ee65..0000000000 --- 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 686cfa6171..94fd60c38c 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 */ -- 2.40.0