]> granicus.if.org Git - postgresql/commitdiff
MERGE post-commit review
authorSimon Riggs <simon@2ndQuadrant.com>
Thu, 5 Apr 2018 08:54:07 +0000 (09:54 +0100)
committerSimon Riggs <simon@2ndQuadrant.com>
Thu, 5 Apr 2018 08:54:07 +0000 (09:54 +0100)
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
src/backend/executor/Makefile
src/backend/executor/README
src/backend/executor/execMerge.c [moved from src/backend/executor/nodeMerge.c with 82% similarity]
src/backend/executor/execPartition.c
src/backend/executor/nodeModifyTable.c
src/backend/optimizer/plan/setrefs.c
src/backend/parser/gram.y
src/include/executor/execMerge.h [new file with mode: 0644]
src/include/executor/nodeMerge.h [deleted file]
src/include/executor/nodeModifyTable.h

index e71f921fda1d44dd97ded4c108c280125520645a..a189356cada8c538017ee379eea4229877acb1c5 100644 (file)
@@ -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);
                }
 
                /*
index 68675f97966dad1e6e6516d59b1e74854bd490f6..76d87eea49c27397f8f9ac09557c6e93c190fc05 100644 (file)
@@ -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 \
index 05769772b77232438930c245c4b87196dbf94577..7882ce44e789c13f8d107197b08f78c0dec32fb3 100644 (file)
@@ -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...
 
similarity index 82%
rename from src/backend/executor/nodeMerge.c
rename to src/backend/executor/execMerge.c
index 0e0d0795d4da7039090c106a5b81da8f5d22c71f..471f64361d3f8ed370d09d79064206c8875e9e18 100644 (file)
@@ -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"
 #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;
+       }
 }
index a6a7885abd154436cb10f587dc766ff88096b3de..007f00569cef2562da1a1c0e3a1d6789f4a4312b 100644 (file)
@@ -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;
 }
 
index b03db64e8e1fde68c8350bca9f351d60e162e6d7..0ebf37bd24019e36646231e99c440dc9c1db4682 100644 (file)
@@ -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;
index cd540a0df5b415bd717b21e2367fd7ff0a7459f1..833a92f5387fdf220f6e8d4b22cb465fd592943c 100644 (file)
@@ -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)
                                {
index b879358de164889ba1d821ea0f8c6366f1aae687..1592b58bb4703df0ce542b671057772181f60939 100644 (file)
@@ -585,7 +585,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <list>           hash_partbound partbound_datum_list range_datum_list
 %type <defelt>         hash_partbound_elem
 
-%type <node>   merge_when_clause opt_and_condition
+%type <node>   merge_when_clause opt_merge_when_and_condition
 %type <list>   merge_when_list
 %type <node>   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 (file)
index 0000000..5ea8c4e
--- /dev/null
@@ -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 (file)
index c222e9e..0000000
+++ /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 */
index 686cfa61710f46fd178533538d98df7e4756440f..94fd60c38cfb203e879f1ef30ce577e29f4eb6f6 100644 (file)
@@ -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 */