aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/executor')
-rw-r--r--src/backend/executor/Makefile2
-rw-r--r--src/backend/executor/README11
-rw-r--r--src/backend/executor/execMain.c17
-rw-r--r--src/backend/executor/execMerge.c682
-rw-r--r--src/backend/executor/execPartition.c121
-rw-r--r--src/backend/executor/execReplication.c4
-rw-r--r--src/backend/executor/nodeModifyTable.c280
-rw-r--r--src/backend/executor/spi.c3
8 files changed, 48 insertions, 1072 deletions
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 76d87eea49c..cc09895fa5c 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 execMerge.o execParallel.o execPartition.o execProcnode.o \
+ execMain.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 \
diff --git a/src/backend/executor/README b/src/backend/executor/README
index 67736805c26..0d7cd552eb6 100644
--- a/src/backend/executor/README
+++ b/src/backend/executor/README
@@ -37,17 +37,6 @@ the plan tree returns the computed tuples to be updated, plus a "junk"
one. For DELETE, the plan tree need only deliver a CTID column, and the
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 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 partitioned table. When a matching target tuple is found,
-the CTID column identifies the matching 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/execMain.c b/src/backend/executor/execMain.c
index 13ad92745e0..4fa713bbe5e 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -233,7 +233,6 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
case CMD_INSERT:
case CMD_DELETE:
case CMD_UPDATE:
- case CMD_MERGE:
estate->es_output_cid = GetCurrentCommandId(true);
break;
@@ -1351,9 +1350,6 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_onConflictArbiterIndexes = NIL;
resultRelInfo->ri_onConflict = NULL;
- resultRelInfo->ri_mergeTargetRTI = 0;
- resultRelInfo->ri_mergeState = (MergeState *) palloc0(sizeof (MergeState));
-
/*
* Partition constraint, which also includes the partition constraint of
* all the ancestors that are partitions. Note that it will be checked
@@ -2203,19 +2199,6 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
errmsg("new row violates row-level security policy for table \"%s\"",
wco->relname)));
break;
- case WCO_RLS_MERGE_UPDATE_CHECK:
- case WCO_RLS_MERGE_DELETE_CHECK:
- if (wco->polname != NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("target row violates row-level security policy \"%s\" (USING expression) for table \"%s\"",
- wco->polname, wco->relname)));
- else
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("target row violates row-level security policy (USING expression) for table \"%s\"",
- wco->relname)));
- break;
case WCO_RLS_CONFLICT_CHECK:
if (wco->polname != NULL)
ereport(ERROR,
diff --git a/src/backend/executor/execMerge.c b/src/backend/executor/execMerge.c
deleted file mode 100644
index d75d7e5ab26..00000000000
--- a/src/backend/executor/execMerge.c
+++ /dev/null
@@ -1,682 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * 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;
- Datum datum;
- bool isNull;
-
- Assert(resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION ||
- resultRelInfo->ri_RelationDesc->rd_rel->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,
- false /* changingPart */);
-
- 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 d4d54e927a5..11139f743d1 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -73,8 +73,6 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
ResultRelInfo *update_rri = NULL;
int num_update_rri = 0,
update_rri_index = 0;
- bool is_update = false;
- bool is_merge = false;
PartitionTupleRouting *proute;
int nparts;
ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
@@ -97,22 +95,13 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
/* Set up details specific to the type of tuple routing we are doing. */
if (node && node->operation == CMD_UPDATE)
- is_update = true;
- else if (node && node->operation == CMD_MERGE)
- is_merge = true;
-
- if (is_update)
{
update_rri = mtstate->resultRelInfo;
num_update_rri = list_length(node->plans);
proute->subplan_partition_offsets =
palloc(num_update_rri * sizeof(int));
proute->num_subplan_partition_offsets = num_update_rri;
- }
-
- if (is_update || is_merge)
- {
/*
* We need an additional tuple slot for storing transient tuples that
* are converted to the root table descriptor.
@@ -296,30 +285,6 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
}
/*
- * Given OID of the partition leaf, return the index of the leaf in the
- * partition hierarchy.
- *
- * XXX This is an O(N) operation and further optimization would be beneficial
- */
-int
-ExecFindPartitionByOid(PartitionTupleRouting *proute, Oid partoid)
-{
- int i;
-
- for (i = 0; i < proute->num_partitions; i++)
- {
- if (proute->partition_oids[i] == partoid)
- break;
- }
-
- if (i >= proute->num_partitions)
- ereport(ERROR,
- (errcode(ERRCODE_INTERNAL_ERROR),
- errmsg("no partition found for OID %u", partoid)));
- return i;
-}
-
-/*
* ExecInitPartitionInfo
* Initialize ResultRelInfo and other information for a partition if not
* already done
@@ -357,8 +322,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
rootrel,
estate->es_instrument);
- leaf_part_rri->ri_PartitionLeafIndex = partidx;
-
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
* attached to the estate as yet. Add it, so that it can be found later.
@@ -635,90 +598,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
Assert(proute->partitions[partidx] == NULL);
proute->partitions[partidx] = leaf_part_rri;
- /*
- * Initialize information about this partition that's needed to handle
- * MERGE.
- */
- if (node && node->operation == CMD_MERGE)
- {
- TupleDesc partrelDesc = RelationGetDescr(partrel);
- TupleConversionMap *map = proute->parent_child_tupconv_maps[partidx];
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
-
- /*
- * If the root parent and partition have the same tuple
- * descriptor, just reuse the original MERGE state for partition.
- */
- if (map == NULL)
- {
- leaf_part_rri->ri_mergeState = resultRelInfo->ri_mergeState;
- }
- else
- {
- /* Convert expressions contain partition's attnos. */
- List *conv_tl, *conv_qual;
- ListCell *l;
- List *matchedActionStates = NIL;
- List *notMatchedActionStates = NIL;
-
- foreach (l, node->mergeActionList)
- {
- MergeAction *action = lfirst_node(MergeAction, l);
- MergeActionState *action_state = makeNode(MergeActionState);
- TupleDesc tupDesc;
- ExprContext *econtext;
-
- action_state->matched = action->matched;
- action_state->commandType = action->commandType;
-
- conv_qual = (List *) action->qual;
- conv_qual = map_partition_varattnos(conv_qual,
- firstVarno, partrel,
- firstResultRel, NULL);
-
- action_state->whenqual = ExecInitQual(conv_qual, &mtstate->ps);
-
- conv_tl = (List *) action->targetList;
- conv_tl = map_partition_varattnos(conv_tl,
- firstVarno, partrel,
- firstResultRel, NULL);
-
- conv_tl = adjust_partition_tlist( conv_tl, map);
-
- tupDesc = ExecTypeFromTL(conv_tl, partrelDesc->tdhasoid);
- action_state->tupDesc = tupDesc;
-
- /* build action projection state */
- econtext = mtstate->ps.ps_ExprContext;
- action_state->proj =
- ExecBuildProjectionInfo(conv_tl, econtext,
- mtstate->mt_mergeproj,
- &mtstate->ps,
- partrelDesc);
-
- if (action_state->matched)
- matchedActionStates =
- lappend(matchedActionStates, action_state);
- else
- notMatchedActionStates =
- lappend(notMatchedActionStates, action_state);
- }
- leaf_part_rri->ri_mergeState->matchedActionStates =
- matchedActionStates;
- leaf_part_rri->ri_mergeState->notMatchedActionStates =
- notMatchedActionStates;
- }
-
- /*
- * get_partition_dispatch_recurse() and expand_partitioned_rtentry()
- * fetch the leaf OIDs in the same order. So we can safely derive the
- * index of the merge target relation corresponding to this partition
- * by simply adding partidx + 1 to the root's merge target relation.
- */
- leaf_part_rri->ri_mergeTargetRTI = node->mergeTargetRelation +
- partidx + 1;
- }
MemoryContextSwitchTo(oldContext);
return leaf_part_rri;
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index b66346702dc..0333ccd0fed 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -464,7 +464,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
{
slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
&searchslot->tts_tuple->t_self,
- NULL, slot, NULL);
+ NULL, slot);
if (slot == NULL) /* "do nothing" */
skip_tuple = true;
@@ -525,7 +525,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
{
skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
&searchslot->tts_tuple->t_self,
- NULL, NULL);
+ NULL);
}
if (!skip_tuple)
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 543a735be2b..7ec2c6bcaa8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,7 +42,6 @@
#include "commands/trigger.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
-#include "executor/execMerge.h"
#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
@@ -63,6 +62,11 @@ static bool ExecOnConflictUpdate(ModifyTableState *mtstate,
EState *estate,
bool canSetTag,
TupleTableSlot **returning);
+static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
+ EState *estate,
+ PartitionTupleRouting *proute,
+ ResultRelInfo *targetRelInfo,
+ TupleTableSlot *slot);
static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
static void ExecSetupChildParentMapForTcs(ModifyTableState *mtstate);
static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
@@ -81,7 +85,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.
*/
-void
+static void
ExecCheckPlanOutput(Relation resultRel, List *targetList)
{
TupleDesc resultDesc = RelationGetDescr(resultRel);
@@ -255,12 +259,11 @@ ExecCheckTIDVisible(EState *estate,
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
*/
-extern TupleTableSlot *
+static TupleTableSlot *
ExecInsert(ModifyTableState *mtstate,
TupleTableSlot *slot,
TupleTableSlot *planSlot,
EState *estate,
- MergeActionState *actionState,
bool canSetTag)
{
HeapTuple tuple;
@@ -387,17 +390,9 @@ ExecInsert(ModifyTableState *mtstate,
* partition, we should instead check UPDATE policies, because we are
* executing policies defined on the target table, and not those
* defined on the child partitions.
- *
- * If we're running MERGE, we refer to the action that we're executing
- * to know if we're doing an INSERT or UPDATE to a partition table.
*/
- if (mtstate->operation == CMD_UPDATE)
- wco_kind = WCO_RLS_UPDATE_CHECK;
- else if (mtstate->operation == CMD_MERGE)
- wco_kind = (actionState->commandType == CMD_UPDATE) ?
- WCO_RLS_UPDATE_CHECK : WCO_RLS_INSERT_CHECK;
- else
- wco_kind = WCO_RLS_INSERT_CHECK;
+ wco_kind = (mtstate->operation == CMD_UPDATE) ?
+ WCO_RLS_UPDATE_CHECK : WCO_RLS_INSERT_CHECK;
/*
* ExecWithCheckOptions() will skip any WCOs which are not of the kind
@@ -622,19 +617,10 @@ ExecInsert(ModifyTableState *mtstate,
* passed to foreign table triggers; it is NULL when the foreign
* table has no relevant triggers.
*
- * MERGE passes actionState of the action it's currently executing;
- * regular DELETE passes NULL. This is used by ExecDelete to know if it's
- * being called from MERGE or regular DELETE operation.
- *
- * If the DELETE fails because the tuple is concurrently updated/deleted
- * by this or some other transaction, hufdp is filled with the reason as
- * well as other important information. Currently only MERGE needs this
- * information.
- *
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
*/
-TupleTableSlot *
+static TupleTableSlot *
ExecDelete(ModifyTableState *mtstate,
ItemPointer tupleid,
HeapTuple oldtuple,
@@ -643,8 +629,6 @@ ExecDelete(ModifyTableState *mtstate,
EState *estate,
bool *tupleDeleted,
bool processReturning,
- HeapUpdateFailureData *hufdp,
- MergeActionState *actionState,
bool canSetTag,
bool changingPart)
{
@@ -659,14 +643,6 @@ ExecDelete(ModifyTableState *mtstate,
*tupleDeleted = false;
/*
- * Initialize hufdp. Since the caller is only interested in the failure
- * status, initialize with the state that is used to indicate successful
- * operation.
- */
- if (hufdp)
- hufdp->result = HeapTupleMayBeUpdated;
-
- /*
* get information on the (current) result relation
*/
resultRelInfo = estate->es_result_relation_info;
@@ -679,7 +655,7 @@ ExecDelete(ModifyTableState *mtstate,
bool dodelete;
dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
- tupleid, oldtuple, hufdp);
+ tupleid, oldtuple);
if (!dodelete) /* "do nothing" */
return NULL;
@@ -747,15 +723,6 @@ ldelete:;
true /* wait for commit */ ,
&hufd,
changingPart);
-
- /*
- * Copy the necessary information, if the caller has asked for it. We
- * must do this irrespective of whether the tuple was updated or
- * deleted.
- */
- if (hufdp)
- *hufdp = hufd;
-
switch (result)
{
case HeapTupleSelfUpdated:
@@ -790,11 +757,7 @@ ldelete:;
errmsg("tuple to be updated was already modified by an operation triggered by the current command"),
errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
- /*
- * Else, already deleted by self; nothing to do but inform
- * MERGE about it anyways so that it can take necessary
- * action.
- */
+ /* Else, already deleted by self; nothing to do */
return NULL;
case HeapTupleMayBeUpdated:
@@ -814,19 +777,10 @@ ldelete:;
{
TupleTableSlot *epqslot;
- /*
- * If we're executing MERGE, then the onus of running
- * EvalPlanQual() and handling its outcome lies with the
- * caller.
- */
- if (actionState != NULL)
- return NULL;
-
- /* Normal DELETE path. */
epqslot = EvalPlanQual(estate,
epqstate,
resultRelationDesc,
- GetEPQRangeTableIndex(resultRelInfo),
+ resultRelInfo->ri_RangeTableIndex,
LockTupleExclusive,
&hufd.ctid,
hufd.xmax);
@@ -836,12 +790,7 @@ ldelete:;
goto ldelete;
}
}
-
- /*
- * tuple already deleted; nothing to do. But MERGE might want
- * to handle it differently. We've already filled-in hufdp
- * with sufficient information for MERGE to look at.
- */
+ /* tuple already deleted; nothing to do */
return NULL;
default:
@@ -969,21 +918,10 @@ ldelete:;
* foreign table triggers; it is NULL when the foreign table has
* no relevant triggers.
*
- * MERGE passes actionState of the action it's currently executing;
- * regular UPDATE passes NULL. This is used by ExecUpdate to know if it's
- * being called from MERGE or regular UPDATE operation. ExecUpdate may
- * pass this information to ExecInsert if it ends up running DELETE+INSERT
- * for partition key updates.
- *
- * If the UPDATE fails because the tuple is concurrently updated/deleted
- * by this or some other transaction, hufdp is filled with the reason as
- * well as other important information. Currently only MERGE needs this
- * information.
- *
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
*/
-extern TupleTableSlot *
+static TupleTableSlot *
ExecUpdate(ModifyTableState *mtstate,
ItemPointer tupleid,
HeapTuple oldtuple,
@@ -991,9 +929,6 @@ ExecUpdate(ModifyTableState *mtstate,
TupleTableSlot *planSlot,
EPQState *epqstate,
EState *estate,
- bool *tuple_updated,
- HeapUpdateFailureData *hufdp,
- MergeActionState *actionState,
bool canSetTag)
{
HeapTuple tuple;
@@ -1010,17 +945,6 @@ ExecUpdate(ModifyTableState *mtstate,
if (IsBootstrapProcessingMode())
elog(ERROR, "cannot UPDATE during bootstrap");
- if (tuple_updated)
- *tuple_updated = false;
-
- /*
- * Initialize hufdp. Since the caller is only interested in the failure
- * status, initialize with the state that is used to indicate successful
- * operation.
- */
- if (hufdp)
- hufdp->result = HeapTupleMayBeUpdated;
-
/*
* get the heap tuple out of the tuple table slot, making sure we have a
* writable copy
@@ -1038,7 +962,7 @@ ExecUpdate(ModifyTableState *mtstate,
resultRelInfo->ri_TrigDesc->trig_update_before_row)
{
slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
- tupleid, oldtuple, slot, hufdp);
+ tupleid, oldtuple, slot);
if (slot == NULL) /* "do nothing" */
return NULL;
@@ -1084,6 +1008,7 @@ ExecUpdate(ModifyTableState *mtstate,
}
else
{
+ LockTupleMode lockmode;
bool partition_constraint_failed;
/*
@@ -1162,7 +1087,7 @@ lreplace:;
* processing. We want to return rows from INSERT.
*/
ExecDelete(mtstate, tupleid, oldtuple, planSlot, epqstate,
- estate, &tuple_deleted, false, hufdp, NULL,
+ estate, &tuple_deleted, false,
false /* canSetTag */, true /* changingPart */);
/*
@@ -1199,36 +1124,16 @@ lreplace:;
saved_tcs_map = mtstate->mt_transition_capture->tcs_map;
/*
- * We should convert the tuple into root's tuple descriptor, since
- * ExecInsert() starts the search from root. To do that, we need to
- * retrieve the tuple conversion map for this resultRelInfo.
- *
- * If we're running MERGE then resultRelInfo is per-partition
- * resultRelInfo as initialized in ExecInitPartitionInfo(). Note
- * that we don't expand inheritance for the resultRelation in case
- * of MERGE and hence there is just one subplan. Whereas for
- * regular UPDATE, resultRelInfo is one of the per-subplan
- * resultRelInfos. In either case the position of this partition in
- * tracked in ri_PartitionLeafIndex;
- *
- * Retrieve the map either by looking at the resultRelInfo's
- * position in mtstate->resultRelInfo[] (for UPDATE) or by simply
- * using the ri_PartitionLeafIndex value (for MERGE).
+ * resultRelInfo is one of the per-subplan resultRelInfos. So we
+ * should convert the tuple into root's tuple descriptor, since
+ * ExecInsert() starts the search from root. The tuple conversion
+ * map list is in the order of mtstate->resultRelInfo[], so to
+ * retrieve the one for this resultRel, we need to know the
+ * position of the resultRel in mtstate->resultRelInfo[].
*/
- if (mtstate->operation == CMD_MERGE)
- {
- map_index = resultRelInfo->ri_PartitionLeafIndex;
- Assert(mtstate->rootResultRelInfo == NULL);
- tupconv_map = TupConvMapForLeaf(proute,
- mtstate->resultRelInfo,
- map_index);
- }
- else
- {
- map_index = resultRelInfo - mtstate->resultRelInfo;
- Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
- tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
- }
+ map_index = resultRelInfo - mtstate->resultRelInfo;
+ Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
+ tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
tuple = ConvertPartitionTupleSlot(tupconv_map,
tuple,
proute->root_tuple_slot,
@@ -1238,16 +1143,12 @@ lreplace:;
* Prepare for tuple routing, making it look like we're inserting
* into the root.
*/
+ Assert(mtstate->rootResultRelInfo != NULL);
slot = ExecPrepareTupleRouting(mtstate, estate, proute,
- getTargetResultRelInfo(mtstate),
- slot);
+ mtstate->rootResultRelInfo, slot);
ret_slot = ExecInsert(mtstate, slot, planSlot,
- estate, actionState, canSetTag);
-
- /* Update is successful. */
- if (tuple_updated)
- *tuple_updated = true;
+ estate, canSetTag);
/* Revert ExecPrepareTupleRouting's node change. */
estate->es_result_relation_info = resultRelInfo;
@@ -1285,16 +1186,7 @@ lreplace:;
estate->es_output_cid,
estate->es_crosscheck_snapshot,
true /* wait for commit */ ,
- &hufd);
-
- /*
- * Copy the necessary information, if the caller has asked for it. We
- * must do this irrespective of whether the tuple was updated or
- * deleted.
- */
- if (hufdp)
- *hufdp = hufd;
-
+ &hufd, &lockmode);
switch (result)
{
case HeapTupleSelfUpdated:
@@ -1348,37 +1240,22 @@ lreplace:;
{
TupleTableSlot *epqslot;
- /*
- * If we're executing MERGE, then the onus of running
- * EvalPlanQual() and handling its outcome lies with the
- * caller.
- */
- if (actionState != NULL)
- return NULL;
-
- /* Regular UPDATE path. */
epqslot = EvalPlanQual(estate,
epqstate,
resultRelationDesc,
- GetEPQRangeTableIndex(resultRelInfo),
- hufd.lockmode,
+ resultRelInfo->ri_RangeTableIndex,
+ lockmode,
&hufd.ctid,
hufd.xmax);
if (!TupIsNull(epqslot))
{
*tupleid = hufd.ctid;
- /* Normal UPDATE path */
slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
tuple = ExecMaterializeSlot(slot);
goto lreplace;
}
}
-
- /*
- * tuple already deleted; nothing to do. But MERGE might want
- * to handle it differently. We've already filled-in hufdp
- * with sufficient information for MERGE to look at.
- */
+ /* tuple already deleted; nothing to do */
return NULL;
default:
@@ -1407,9 +1284,6 @@ lreplace:;
estate, false, NULL, NIL);
}
- if (tuple_updated)
- *tuple_updated = true;
-
if (canSetTag)
(estate->es_processed)++;
@@ -1504,9 +1378,9 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
* there's no historical behavior to break.
*
* It is the user's responsibility to prevent this situation from
- * occurring. These problems are why SQL Standard similarly
- * specifies that for SQL MERGE, an exception must be raised in
- * the event of an attempt to update the same row twice.
+ * occurring. These problems are why SQL-2003 similarly specifies
+ * that for SQL MERGE, an exception must be raised in the event of
+ * an attempt to update the same row twice.
*/
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple.t_data)))
ereport(ERROR,
@@ -1636,7 +1510,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
*returning = ExecUpdate(mtstate, &tuple.t_self, NULL,
mtstate->mt_conflproj, planSlot,
&mtstate->mt_epqstate, mtstate->ps.state,
- NULL, NULL, NULL, canSetTag);
+ canSetTag);
ReleaseBuffer(buffer);
return true;
@@ -1674,14 +1548,6 @@ fireBSTriggers(ModifyTableState *node)
case CMD_DELETE:
ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
break;
- case CMD_MERGE:
- if (node->mt_merge_subcommands & MERGE_INSERT)
- ExecBSInsertTriggers(node->ps.state, resultRelInfo);
- if (node->mt_merge_subcommands & MERGE_UPDATE)
- ExecBSUpdateTriggers(node->ps.state, resultRelInfo);
- if (node->mt_merge_subcommands & MERGE_DELETE)
- ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
- break;
default:
elog(ERROR, "unknown operation");
break;
@@ -1737,17 +1603,6 @@ fireASTriggers(ModifyTableState *node)
ExecASDeleteTriggers(node->ps.state, resultRelInfo,
node->mt_transition_capture);
break;
- case CMD_MERGE:
- if (node->mt_merge_subcommands & MERGE_DELETE)
- ExecASDeleteTriggers(node->ps.state, resultRelInfo,
- node->mt_transition_capture);
- if (node->mt_merge_subcommands & MERGE_UPDATE)
- ExecASUpdateTriggers(node->ps.state, resultRelInfo,
- node->mt_transition_capture);
- if (node->mt_merge_subcommands & MERGE_INSERT)
- ExecASInsertTriggers(node->ps.state, resultRelInfo,
- node->mt_transition_capture);
- break;
default:
elog(ERROR, "unknown operation");
break;
@@ -1810,7 +1665,7 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
*
* Returns a slot holding the tuple of the partition rowtype.
*/
-TupleTableSlot *
+static TupleTableSlot *
ExecPrepareTupleRouting(ModifyTableState *mtstate,
EState *estate,
PartitionTupleRouting *proute,
@@ -2143,7 +1998,6 @@ ExecModifyTable(PlanState *pstate)
{
/* advance to next subplan if any */
node->mt_whichplan++;
-
if (node->mt_whichplan < node->mt_nplans)
{
resultRelInfo++;
@@ -2192,12 +2046,6 @@ ExecModifyTable(PlanState *pstate)
EvalPlanQualSetSlot(&node->mt_epqstate, planSlot);
slot = planSlot;
- if (operation == CMD_MERGE)
- {
- ExecMerge(node, estate, slot, junkfilter, resultRelInfo);
- continue;
- }
-
tupleid = NULL;
oldtuple = NULL;
if (junkfilter != NULL)
@@ -2279,20 +2127,19 @@ ExecModifyTable(PlanState *pstate)
slot = ExecPrepareTupleRouting(node, estate, proute,
resultRelInfo, slot);
slot = ExecInsert(node, slot, planSlot,
- estate, NULL, node->canSetTag);
+ estate, node->canSetTag);
/* Revert ExecPrepareTupleRouting's state change. */
if (proute)
estate->es_result_relation_info = resultRelInfo;
break;
case CMD_UPDATE:
slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
- &node->mt_epqstate, estate,
- NULL, NULL, NULL, node->canSetTag);
+ &node->mt_epqstate, estate, node->canSetTag);
break;
case CMD_DELETE:
slot = ExecDelete(node, tupleid, oldtuple, planSlot,
&node->mt_epqstate, estate,
- NULL, true, NULL, NULL, node->canSetTag,
+ NULL, true, node->canSetTag,
false /* changingPart */);
break;
default:
@@ -2383,16 +2230,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
saved_resultRelInfo = estate->es_result_relation_info;
resultRelInfo = mtstate->resultRelInfo;
-
- /*
- * mergeTargetRelation must be set if we're running MERGE and mustn't be
- * set if we're not.
- */
- Assert(operation != CMD_MERGE || node->mergeTargetRelation > 0);
- Assert(operation == CMD_MERGE || node->mergeTargetRelation == 0);
-
- resultRelInfo->ri_mergeTargetRTI = node->mergeTargetRelation;
-
i = 0;
foreach(l, node->plans)
{
@@ -2471,8 +2308,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* partition key.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
- (operation == CMD_INSERT || operation == CMD_MERGE ||
- update_tuple_routing_needed))
+ (operation == CMD_INSERT || update_tuple_routing_needed))
mtstate->mt_partition_tuple_routing =
ExecSetupPartitionTupleRouting(mtstate, rel);
@@ -2484,15 +2320,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupTransitionCaptureState(mtstate, estate);
/*
- * If we are doing MERGE then setup child-parent mapping. This will be
- * required in case we end up doing a partition-key update, triggering a
- * tuple routing.
- */
- if (mtstate->operation == CMD_MERGE &&
- mtstate->mt_partition_tuple_routing != NULL)
- ExecSetupChildParentMapForLeaf(mtstate->mt_partition_tuple_routing);
-
- /*
* Construct mapping from each of the per-subplan partition attnos to the
* root attno. This is required when during update row movement the tuple
* descriptor of a source partition does not match the root partitioned
@@ -2684,10 +2511,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
}
- resultRelInfo = mtstate->resultRelInfo;
- if (mtstate->operation == CMD_MERGE)
- ExecInitMerge(mtstate, estate, resultRelInfo);
-
/* select first subplan */
mtstate->mt_whichplan = 0;
subplan = (Plan *) linitial(node->plans);
@@ -2701,7 +2524,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* --- no need to look first. Typically, this will be a 'ctid' or
* 'wholerow' attribute, but in the case of a foreign data wrapper it
* might be a set of junk attributes sufficient to identify the remote
- * row. We follow this logic for MERGE, so it always has a junk attributes.
+ * row.
*
* If there are multiple result relations, each one needs its own junk
* filter. Note multiple rels are only possible for UPDATE/DELETE, so we
@@ -2729,7 +2552,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
break;
case CMD_UPDATE:
case CMD_DELETE:
- case CMD_MERGE:
junk_filter_needed = true;
break;
default:
@@ -2745,7 +2567,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
JunkFilter *j;
subplan = mtstate->mt_plans[i]->plan;
-
if (operation == CMD_INSERT || operation == CMD_UPDATE)
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
subplan->targetlist);
@@ -2754,9 +2575,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
ExecInitExtraTupleSlot(estate, NULL));
- if (operation == CMD_UPDATE ||
- operation == CMD_DELETE ||
- operation == CMD_MERGE)
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
/* For UPDATE/DELETE, find the appropriate junk attr now */
char relkind;
@@ -2769,15 +2588,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
if (!AttributeNumberIsValid(j->jf_junkAttNo))
elog(ERROR, "could not find junk ctid column");
-
- if (operation == CMD_MERGE &&
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_otherJunkAttNo = ExecFindJunkAttribute(j, "tableoid");
- if (!AttributeNumberIsValid(j->jf_otherJunkAttNo))
- elog(ERROR, "could not find junk tableoid column");
-
- }
}
else if (relkind == RELKIND_FOREIGN_TABLE)
{
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index a49015e7cbc..08f6f67a15c 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2420,9 +2420,6 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount)
else
res = SPI_OK_UPDATE;
break;
- case CMD_MERGE:
- res = SPI_OK_MERGE;
- break;
default:
return SPI_ERROR_OPUNKNOWN;
}