diff options
author | Simon Riggs <simon@2ndQuadrant.com> | 2018-04-12 11:22:56 +0100 |
---|---|---|
committer | Simon Riggs <simon@2ndQuadrant.com> | 2018-04-12 11:22:56 +0100 |
commit | 08ea7a2291db21a618d19d612c8060cda68f1892 (patch) | |
tree | 4d10675439742c7206e089bd21e793332562ae83 /src/backend/executor | |
parent | c9c875a28fa6cbc38c227fb9e656dd7be948166f (diff) | |
download | postgresql-08ea7a2291db21a618d19d612c8060cda68f1892.tar.gz postgresql-08ea7a2291db21a618d19d612c8060cda68f1892.zip |
Revert MERGE patch
This reverts commits d204ef63776b8a00ca220adec23979091564e465,
83454e3c2b28141c0db01c7d2027e01040df5249 and a few more commits thereafter
(complete list at the end) related to MERGE feature.
While the feature was fully functional, with sufficient test coverage and
necessary documentation, it was felt that some parts of the executor and
parse-analyzer can use a different design and it wasn't possible to do that in
the available time. So it was decided to revert the patch for PG11 and retry
again in the future.
Thanks again to all reviewers and bug reporters.
List of commits reverted, in reverse chronological order:
f1464c5380 Improve parse representation for MERGE
ddb4158579 MERGE syntax diagram correction
530e69e59b Allow cpluspluscheck to pass by renaming variable
01b88b4df5 MERGE minor errata
3af7b2b0d4 MERGE fix variable warning in non-assert builds
a5d86181ec MERGE INSERT allows only one VALUES clause
4b2d44031f MERGE post-commit review
4923550c20 Tab completion for MERGE
aa3faa3c7a WITH support in MERGE
83454e3c2b New files for MERGE
d204ef6377 MERGE SQL Command following SQL:2016
Author: Pavan Deolasee
Reviewed-by: Michael Paquier
Diffstat (limited to 'src/backend/executor')
-rw-r--r-- | src/backend/executor/Makefile | 2 | ||||
-rw-r--r-- | src/backend/executor/README | 11 | ||||
-rw-r--r-- | src/backend/executor/execMain.c | 17 | ||||
-rw-r--r-- | src/backend/executor/execMerge.c | 682 | ||||
-rw-r--r-- | src/backend/executor/execPartition.c | 121 | ||||
-rw-r--r-- | src/backend/executor/execReplication.c | 4 | ||||
-rw-r--r-- | src/backend/executor/nodeModifyTable.c | 280 | ||||
-rw-r--r-- | src/backend/executor/spi.c | 3 |
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; } |