aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/executor/nodeMerge.c575
-rw-r--r--src/backend/parser/parse_merge.c660
2 files changed, 0 insertions, 1235 deletions
diff --git a/src/backend/executor/nodeMerge.c b/src/backend/executor/nodeMerge.c
deleted file mode 100644
index 0e0d0795d4d..00000000000
--- a/src/backend/executor/nodeMerge.c
+++ /dev/null
@@ -1,575 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * nodeMerge.c
- * routines to handle Merge nodes relating to the MERGE command
- *
- * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- *
- * IDENTIFICATION
- * src/backend/executor/nodeMerge.c
- *
- *-------------------------------------------------------------------------
- */
-
-
-#include "postgres.h"
-
-#include "access/htup_details.h"
-#include "access/xact.h"
-#include "commands/trigger.h"
-#include "executor/execPartition.h"
-#include "executor/executor.h"
-#include "executor/nodeModifyTable.h"
-#include "executor/nodeMerge.h"
-#include "miscadmin.h"
-#include "nodes/nodeFuncs.h"
-#include "storage/bufmgr.h"
-#include "storage/lmgr.h"
-#include "utils/builtins.h"
-#include "utils/memutils.h"
-#include "utils/rel.h"
-#include "utils/tqual.h"
-
-
-/*
- * Check and execute the first qualifying MATCHED action. The current target
- * tuple is identified by tupleid.
- *
- * We start from the first WHEN MATCHED action and check if the WHEN AND quals
- * pass, if any. If the WHEN AND quals for the first action do not pass, we
- * check the second, then the third and so on. If we reach to the end, no
- * action is taken and we return true, indicating that no further action is
- * required for this tuple.
- *
- * If we do find a qualifying action, then we attempt to execute the action.
- *
- * If the tuple is concurrently updated, EvalPlanQual is run with the updated
- * tuple to recheck the join quals. Note that the additional quals associated
- * with individual actions are evaluated separately by the MERGE code, while
- * EvalPlanQual checks for the join quals. If EvalPlanQual tells us that the
- * updated tuple still passes the join quals, then we restart from the first
- * action to look for a qualifying action. Otherwise, we return false meaning
- * that a NOT MATCHED action must now be executed for the current source tuple.
- */
-static bool
-ExecMergeMatched(ModifyTableState *mtstate, EState *estate,
- TupleTableSlot *slot, JunkFilter *junkfilter,
- ItemPointer tupleid)
-{
- ExprContext *econtext = mtstate->ps.ps_ExprContext;
- bool isNull;
- List *mergeMatchedActionStates = NIL;
- HeapUpdateFailureData hufd;
- bool tuple_updated,
- tuple_deleted;
- Buffer buffer;
- HeapTupleData tuple;
- EPQState *epqstate = &mtstate->mt_epqstate;
- ResultRelInfo *saved_resultRelInfo;
- ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
- ListCell *l;
- TupleTableSlot *saved_slot = slot;
-
- if (mtstate->mt_partition_tuple_routing)
- {
- Datum datum;
- Oid tableoid = InvalidOid;
- int leaf_part_index;
- PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
-
- /*
- * In case of partitioned table, we fetch the tableoid while performing
- * MATCHED MERGE action.
- */
- datum = ExecGetJunkAttribute(slot, junkfilter->jf_otherJunkAttNo,
- &isNull);
- Assert(!isNull);
- tableoid = DatumGetObjectId(datum);
-
- /*
- * If we're dealing with a MATCHED tuple, then tableoid must have been
- * set correctly. In case of partitioned table, we must now fetch the
- * correct result relation corresponding to the child table emitting
- * the matching target row. For normal table, there is just one result
- * relation and it must be the one emitting the matching row.
- */
- leaf_part_index = ExecFindPartitionByOid(proute, tableoid);
-
- resultRelInfo = proute->partitions[leaf_part_index];
- if (resultRelInfo == NULL)
- {
- resultRelInfo = ExecInitPartitionInfo(mtstate,
- mtstate->resultRelInfo,
- proute, estate, leaf_part_index);
- Assert(resultRelInfo != NULL);
- }
- }
-
- /*
- * Save the current information and work with the correct result relation.
- */
- saved_resultRelInfo = resultRelInfo;
- estate->es_result_relation_info = resultRelInfo;
-
- /*
- * And get the correct action lists.
- */
- mergeMatchedActionStates =
- resultRelInfo->ri_mergeState->matchedActionStates;
-
- /*
- * If there are not WHEN MATCHED actions, we are done.
- */
- if (mergeMatchedActionStates == NIL)
- return true;
-
- /*
- * Make tuple and any needed join variables available to ExecQual and
- * ExecProject. The target's existing tuple is installed in the scantuple.
- * Again, this target relation's slot is required only in the case of a
- * MATCHED tuple and UPDATE/DELETE actions.
- */
- if (mtstate->mt_partition_tuple_routing)
- ExecSetSlotDescriptor(mtstate->mt_existing,
- resultRelInfo->ri_RelationDesc->rd_att);
- econtext->ecxt_scantuple = mtstate->mt_existing;
- econtext->ecxt_innertuple = slot;
- econtext->ecxt_outertuple = NULL;
-
-lmerge_matched:;
- slot = saved_slot;
-
- /*
- * UPDATE/DELETE is only invoked for matched rows. And we must have found
- * the tupleid of the target row in that case. We fetch using SnapshotAny
- * because we might get called again after EvalPlanQual returns us a new
- * tuple. This tuple may not be visible to our MVCC snapshot.
- */
- Assert(tupleid != NULL);
-
- tuple.t_self = *tupleid;
- if (!heap_fetch(resultRelInfo->ri_RelationDesc, SnapshotAny, &tuple,
- &buffer, true, NULL))
- elog(ERROR, "Failed to fetch the target tuple");
-
- /* Store target's existing tuple in the state's dedicated slot */
- ExecStoreTuple(&tuple, mtstate->mt_existing, buffer, false);
-
- foreach(l, mergeMatchedActionStates)
- {
- MergeActionState *action = (MergeActionState *) lfirst(l);
-
- /*
- * Test condition, if any
- *
- * In the absence of a condition we perform the action unconditionally
- * (no need to check separately since ExecQual() will return true if
- * there are no conditions to evaluate).
- */
- if (!ExecQual(action->whenqual, econtext))
- continue;
-
- /*
- * Check if the existing target tuple meet the USING checks of
- * UPDATE/DELETE RLS policies. If those checks fail, we throw an
- * error.
- *
- * The WITH CHECK quals are applied in ExecUpdate() and hence we need
- * not do anything special to handle them.
- *
- * NOTE: We must do this after WHEN quals are evaluated so that we
- * check policies only when they matter.
- */
- if (resultRelInfo->ri_WithCheckOptions)
- {
- ExecWithCheckOptions(action->commandType == CMD_UPDATE ?
- WCO_RLS_MERGE_UPDATE_CHECK : WCO_RLS_MERGE_DELETE_CHECK,
- resultRelInfo,
- mtstate->mt_existing,
- mtstate->ps.state);
- }
-
- /* Perform stated action */
- switch (action->commandType)
- {
- case CMD_UPDATE:
-
- /*
- * We set up the projection earlier, so all we do here is
- * Project, no need for any other tasks prior to the
- * ExecUpdate.
- */
- if (mtstate->mt_partition_tuple_routing)
- ExecSetSlotDescriptor(mtstate->mt_mergeproj, action->tupDesc);
- ExecProject(action->proj);
-
- /*
- * We don't call ExecFilterJunk() because the projected tuple
- * using the UPDATE action's targetlist doesn't have a junk
- * attribute.
- */
- slot = ExecUpdate(mtstate, tupleid, NULL,
- mtstate->mt_mergeproj,
- slot, epqstate, estate,
- &tuple_updated, &hufd,
- action, mtstate->canSetTag);
- break;
-
- case CMD_DELETE:
- /* Nothing to Project for a DELETE action */
- slot = ExecDelete(mtstate, tupleid, NULL,
- slot, epqstate, estate,
- &tuple_deleted, false, &hufd, action,
- mtstate->canSetTag);
-
- break;
-
- default:
- elog(ERROR, "unknown action in MERGE WHEN MATCHED clause");
-
- }
-
- /*
- * Check for any concurrent update/delete operation which may have
- * prevented our update/delete. We also check for situations where we
- * might be trying to update/delete the same tuple twice.
- */
- if ((action->commandType == CMD_UPDATE && !tuple_updated) ||
- (action->commandType == CMD_DELETE && !tuple_deleted))
-
- {
- switch (hufd.result)
- {
- case HeapTupleMayBeUpdated:
- break;
- case HeapTupleInvisible:
-
- /*
- * This state should never be reached since the underlying
- * JOIN runs with a MVCC snapshot and should only return
- * rows visible to us.
- */
- elog(ERROR, "unexpected invisible tuple");
- break;
-
- case HeapTupleSelfUpdated:
-
- /*
- * SQLStandard disallows this for MERGE.
- */
- if (TransactionIdIsCurrentTransactionId(hufd.xmax))
- ereport(ERROR,
- (errcode(ERRCODE_CARDINALITY_VIOLATION),
- errmsg("MERGE command cannot affect row a second time"),
- errhint("Ensure that not more than one source row matches any one target row")));
- /* This shouldn't happen */
- elog(ERROR, "attempted to update or delete invisible tuple");
- break;
-
- case HeapTupleUpdated:
-
- /*
- * The target tuple was concurrently updated/deleted by
- * some other transaction.
- *
- * If the current tuple is that last tuple in the update
- * chain, then we know that the tuple was concurrently
- * deleted. Just return and let the caller try NOT MATCHED
- * actions.
- *
- * If the current tuple was concurrently updated, then we
- * must run the EvalPlanQual() with the new version of the
- * tuple. If EvalPlanQual() does not return a tuple then
- * we switch to the NOT MATCHED list of actions.
- * If it does return a tuple and the join qual is
- * still satisfied, then we just need to recheck the
- * MATCHED actions, starting from the top, and execute the
- * first qualifying action.
- */
- if (!ItemPointerEquals(tupleid, &hufd.ctid))
- {
- TupleTableSlot *epqslot;
-
- /*
- * Since we generate a JOIN query with a target table
- * RTE different than the result relation RTE, we must
- * pass in the RTI of the relation used in the join
- * query and not the one from result relation.
- */
- Assert(resultRelInfo->ri_mergeTargetRTI > 0);
- epqslot = EvalPlanQual(estate,
- epqstate,
- resultRelInfo->ri_RelationDesc,
- GetEPQRangeTableIndex(resultRelInfo),
- LockTupleExclusive,
- &hufd.ctid,
- hufd.xmax);
-
- if (!TupIsNull(epqslot))
- {
- (void) ExecGetJunkAttribute(epqslot,
- resultRelInfo->ri_junkFilter->jf_junkAttNo,
- &isNull);
-
- /*
- * A non-NULL ctid means that we are still dealing
- * with MATCHED case. But we must retry from the
- * start with the updated tuple to ensure that the
- * first qualifying WHEN MATCHED action is
- * executed.
- *
- * We don't use the new slot returned by
- * EvalPlanQual because we anyways re-install the
- * new target tuple in econtext->ecxt_scantuple
- * before re-evaluating WHEN AND conditions and
- * re-projecting the update targetlists. The
- * source side tuple does not change and hence we
- * can safely continue to use the old slot.
- */
- if (!isNull)
- {
- /*
- * Must update *tupleid to the TID of the
- * newer tuple found in the update chain.
- */
- *tupleid = hufd.ctid;
- ReleaseBuffer(buffer);
- goto lmerge_matched;
- }
- }
- }
-
- /*
- * Tell the caller about the updated TID, restore the
- * state back and return.
- */
- *tupleid = hufd.ctid;
- estate->es_result_relation_info = saved_resultRelInfo;
- ReleaseBuffer(buffer);
- return false;
-
- default:
- break;
-
- }
- }
-
- if (action->commandType == CMD_UPDATE && tuple_updated)
- InstrCountFiltered2(&mtstate->ps, 1);
- if (action->commandType == CMD_DELETE && tuple_deleted)
- InstrCountFiltered3(&mtstate->ps, 1);
-
- /*
- * We've activated one of the WHEN clauses, so we don't search
- * further. This is required behaviour, not an optimization.
- */
- estate->es_result_relation_info = saved_resultRelInfo;
- break;
- }
-
- ReleaseBuffer(buffer);
-
- /*
- * Successfully executed an action or no qualifying action was found.
- */
- return true;
-}
-
-/*
- * Execute the first qualifying NOT MATCHED action.
- */
-static void
-ExecMergeNotMatched(ModifyTableState *mtstate, EState *estate,
- TupleTableSlot *slot)
-{
- PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
- ExprContext *econtext = mtstate->ps.ps_ExprContext;
- List *mergeNotMatchedActionStates = NIL;
- ResultRelInfo *resultRelInfo;
- ListCell *l;
- TupleTableSlot *myslot;
-
- /*
- * We are dealing with NOT MATCHED tuple. Since for MERGE, partition tree
- * is not expanded for the result relation, we continue to work with the
- * currently active result relation, which should be of the root of the
- * partition tree.
- */
- resultRelInfo = mtstate->resultRelInfo;
-
- /*
- * For INSERT actions, root relation's merge action is OK since the
- * INSERT's targetlist and the WHEN conditions can only refer to the
- * source relation and hence it does not matter which result relation we
- * work with.
- */
- mergeNotMatchedActionStates =
- resultRelInfo->ri_mergeState->notMatchedActionStates;
-
- /*
- * Make source tuple available to ExecQual and ExecProject. We don't need
- * the target tuple since the WHEN quals and the targetlist can't refer to
- * the target columns.
- */
- econtext->ecxt_scantuple = NULL;
- econtext->ecxt_innertuple = slot;
- econtext->ecxt_outertuple = NULL;
-
- foreach(l, mergeNotMatchedActionStates)
- {
- MergeActionState *action = (MergeActionState *) lfirst(l);
-
- /*
- * Test condition, if any
- *
- * In the absence of a condition we perform the action unconditionally
- * (no need to check separately since ExecQual() will return true if
- * there are no conditions to evaluate).
- */
- if (!ExecQual(action->whenqual, econtext))
- continue;
-
- /* Perform stated action */
- switch (action->commandType)
- {
- case CMD_INSERT:
-
- /*
- * We set up the projection earlier, so all we do here is
- * Project, no need for any other tasks prior to the
- * ExecInsert.
- */
- if (mtstate->mt_partition_tuple_routing)
- ExecSetSlotDescriptor(mtstate->mt_mergeproj, action->tupDesc);
- ExecProject(action->proj);
-
- /*
- * ExecPrepareTupleRouting may modify the passed-in slot. Hence
- * pass a local reference so that action->slot is not modified.
- */
- myslot = mtstate->mt_mergeproj;
-
- /* Prepare for tuple routing if needed. */
- if (proute)
- myslot = ExecPrepareTupleRouting(mtstate, estate, proute,
- resultRelInfo, myslot);
- slot = ExecInsert(mtstate, myslot, slot,
- estate, action,
- mtstate->canSetTag);
- /* Revert ExecPrepareTupleRouting's state change. */
- if (proute)
- estate->es_result_relation_info = resultRelInfo;
- InstrCountFiltered1(&mtstate->ps, 1);
- break;
- case CMD_NOTHING:
- /* Do Nothing */
- break;
- default:
- elog(ERROR, "unknown action in MERGE WHEN NOT MATCHED clause");
- }
-
- break;
- }
-}
-
-/*
- * Perform MERGE.
- */
-void
-ExecMerge(ModifyTableState *mtstate, EState *estate, TupleTableSlot *slot,
- JunkFilter *junkfilter, ResultRelInfo *resultRelInfo)
-{
- ExprContext *econtext = mtstate->ps.ps_ExprContext;
- ItemPointer tupleid;
- ItemPointerData tuple_ctid;
- bool matched = false;
- char relkind;
- Datum datum;
- bool isNull;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- Assert(relkind == RELKIND_RELATION ||
- relkind == RELKIND_PARTITIONED_TABLE);
-
- /*
- * Reset per-tuple memory context to free any expression evaluation
- * storage allocated in the previous cycle.
- */
- ResetExprContext(econtext);
-
- /*
- * We run a JOIN between the target relation and the source relation to
- * find a set of candidate source rows that has matching row in the target
- * table and a set of candidate source rows that does not have matching
- * row in the target table. If the join returns us a tuple with target
- * relation's tid set, that implies that the join found a matching row for
- * the given source tuple. This case triggers the WHEN MATCHED clause of
- * the MERGE. Whereas a NULL in the target relation's ctid column
- * indicates a NOT MATCHED case.
- */
- datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, &isNull);
-
- if (!isNull)
- {
- matched = true;
- tupleid = (ItemPointer) DatumGetPointer(datum);
- tuple_ctid = *tupleid; /* be sure we don't free ctid!! */
- tupleid = &tuple_ctid;
- }
- else
- {
- matched = false;
- tupleid = NULL; /* we don't need it for INSERT actions */
- }
-
- /*
- * If we are dealing with a WHEN MATCHED case, we execute the first action
- * for which the additional WHEN MATCHED AND quals pass. If an action
- * without quals is found, that action is executed.
- *
- * Similarly, if we are dealing with WHEN NOT MATCHED case, we look at the
- * given WHEN NOT MATCHED actions in sequence until one passes.
- *
- * Things get interesting in case of concurrent update/delete of the
- * target tuple. Such concurrent update/delete is detected while we are
- * executing a WHEN MATCHED action.
- *
- * A concurrent update can:
- *
- * 1. modify the target tuple so that it no longer satisfies the
- * additional quals attached to the current WHEN MATCHED action OR
- *
- * In this case, we are still dealing with a WHEN MATCHED case, but
- * we should recheck the list of WHEN MATCHED actions and choose the first
- * one that satisfies the new target tuple.
- *
- * 2. modify the target tuple so that the join quals no longer pass and
- * hence the source tuple no longer has a match.
- *
- * In the second case, the source tuple no longer matches the target tuple,
- * so we now instead find a qualifying WHEN NOT MATCHED action to execute.
- *
- * A concurrent delete, changes a WHEN MATCHED case to WHEN NOT MATCHED.
- *
- * ExecMergeMatched takes care of following the update chain and
- * re-finding the qualifying WHEN MATCHED action, as long as the updated
- * target tuple still satisfies the join quals i.e. it still remains a
- * WHEN MATCHED case. If the tuple gets deleted or the join quals fail, it
- * returns and we try ExecMergeNotMatched. Given that ExecMergeMatched
- * always make progress by following the update chain and we never switch
- * from ExecMergeNotMatched to ExecMergeMatched, there is no risk of a
- * livelock.
- */
- if (matched)
- matched = ExecMergeMatched(mtstate, estate, slot, junkfilter, tupleid);
-
- /*
- * Either we were dealing with a NOT MATCHED tuple or ExecMergeNotMatched()
- * returned "false", indicating the previously MATCHED tuple is no longer a
- * matching tuple.
- */
- if (!matched)
- ExecMergeNotMatched(mtstate, estate, slot);
-}
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
deleted file mode 100644
index e073d93daf6..00000000000
--- a/src/backend/parser/parse_merge.c
+++ /dev/null
@@ -1,660 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * parse_merge.c
- * handle merge-statement in parser
- *
- * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- *
- * IDENTIFICATION
- * src/backend/parser/parse_merge.c
- *
- *-------------------------------------------------------------------------
- */
-
-#include "postgres.h"
-
-#include "miscadmin.h"
-
-#include "access/sysattr.h"
-#include "nodes/makefuncs.h"
-#include "parser/analyze.h"
-#include "parser/parse_collate.h"
-#include "parser/parsetree.h"
-#include "parser/parser.h"
-#include "parser/parse_clause.h"
-#include "parser/parse_merge.h"
-#include "parser/parse_relation.h"
-#include "parser/parse_target.h"
-#include "utils/rel.h"
-#include "utils/relcache.h"
-
-static int transformMergeJoinClause(ParseState *pstate, Node *merge,
- List **mergeSourceTargetList);
-static void setNamespaceForMergeAction(ParseState *pstate,
- MergeAction *action);
-static void setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte,
- bool rel_visible,
- bool cols_visible);
-static List *expandSourceTL(ParseState *pstate, RangeTblEntry *rte,
- int rtindex);
-
-/*
- * Special handling for MERGE statement is required because we assemble
- * the query manually. This is similar to setTargetTable() followed
- * by transformFromClause() but with a few less steps.
- *
- * Process the FROM clause and add items to the query's range table,
- * joinlist, and namespace.
- *
- * A special targetlist comprising of the columns from the right-subtree of
- * the join is populated and returned. Note that when the JoinExpr is
- * setup by transformMergeStmt, the left subtree has the target result
- * relation and the right subtree has the source relation.
- *
- * Returns the rangetable index of the target relation.
- */
-static int
-transformMergeJoinClause(ParseState *pstate, Node *merge,
- List **mergeSourceTargetList)
-{
- RangeTblEntry *rte,
- *rt_rte;
- List *namespace;
- int rtindex,
- rt_rtindex;
- Node *n;
- int mergeTarget_relation = list_length(pstate->p_rtable) + 1;
- Var *var;
- TargetEntry *te;
-
- n = transformFromClauseItem(pstate, merge,
- &rte,
- &rtindex,
- &rt_rte,
- &rt_rtindex,
- &namespace);
-
- pstate->p_joinlist = list_make1(n);
-
- /*
- * We created an internal join between the target and the source relation
- * to carry out the MERGE actions. Normally such an unaliased join hides
- * the joining relations, unless the column references are qualified.
- * Also, any unqualified column references are resolved to the Join RTE, if
- * there is a matching entry in the targetlist. But the way MERGE
- * execution is later setup, we expect all column references to resolve to
- * either the source or the target relation. Hence we must not add the
- * Join RTE to the namespace.
- *
- * The last entry must be for the top-level Join RTE. We don't want to
- * resolve any references to the Join RTE. So discard that.
- *
- * We also do not want to resolve any references from the leftside of the
- * Join since that corresponds to the target relation. References to the
- * columns of the target relation must be resolved from the result
- * relation and not the one that is used in the join. So the
- * mergeTarget_relation is marked invisible to both qualified as well as
- * unqualified references.
- */
- Assert(list_length(namespace) > 1);
- namespace = list_truncate(namespace, list_length(namespace) - 1);
- pstate->p_namespace = list_concat(pstate->p_namespace, namespace);
-
- setNamespaceVisibilityForRTE(pstate->p_namespace,
- rt_fetch(mergeTarget_relation, pstate->p_rtable), false, false);
-
- /*
- * Expand the right relation and add its columns to the
- * mergeSourceTargetList. Note that the right relation can either be a
- * plain relation or a subquery or anything that can have a
- * RangeTableEntry.
- */
- *mergeSourceTargetList = expandSourceTL(pstate, rt_rte, rt_rtindex);
-
- /*
- * Add a whole-row-Var entry to support references to "source.*".
- */
- var = makeWholeRowVar(rt_rte, rt_rtindex, 0, false);
- te = makeTargetEntry((Expr *) var, list_length(*mergeSourceTargetList) + 1,
- NULL, true);
- *mergeSourceTargetList = lappend(*mergeSourceTargetList, te);
-
- return mergeTarget_relation;
-}
-
-/*
- * Make appropriate changes to the namespace visibility while transforming
- * individual action's quals and targetlist expressions. In particular, for
- * INSERT actions we must only see the source relation (since INSERT action is
- * invoked for NOT MATCHED tuples and hence there is no target tuple to deal
- * with). On the other hand, UPDATE and DELETE actions can see both source and
- * target relations.
- *
- * Also, since the internal Join node can hide the source and target
- * relations, we must explicitly make the respective relation as visible so
- * that columns can be referenced unqualified from these relations.
- */
-static void
-setNamespaceForMergeAction(ParseState *pstate, MergeAction *action)
-{
- RangeTblEntry *targetRelRTE,
- *sourceRelRTE;
-
- /* Assume target relation is at index 1 */
- targetRelRTE = rt_fetch(1, pstate->p_rtable);
-
- /*
- * Assume that the top-level join RTE is at the end. The source relation
- * is just before that.
- */
- sourceRelRTE = rt_fetch(list_length(pstate->p_rtable) - 1, pstate->p_rtable);
-
- switch (action->commandType)
- {
- case CMD_INSERT:
-
- /*
- * Inserts can't see target relation, but they can see source
- * relation.
- */
- setNamespaceVisibilityForRTE(pstate->p_namespace,
- targetRelRTE, false, false);
- setNamespaceVisibilityForRTE(pstate->p_namespace,
- sourceRelRTE, true, true);
- break;
-
- case CMD_UPDATE:
- case CMD_DELETE:
-
- /*
- * Updates and deletes can see both target and source relations.
- */
- setNamespaceVisibilityForRTE(pstate->p_namespace,
- targetRelRTE, true, true);
- setNamespaceVisibilityForRTE(pstate->p_namespace,
- sourceRelRTE, true, true);
- break;
-
- case CMD_NOTHING:
- break;
- default:
- elog(ERROR, "unknown action in MERGE WHEN clause");
- }
-}
-
-/*
- * transformMergeStmt -
- * transforms a MERGE statement
- */
-Query *
-transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
-{
- Query *qry = makeNode(Query);
- ListCell *l;
- AclMode targetPerms = ACL_NO_RIGHTS;
- bool is_terminal[2];
- JoinExpr *joinexpr;
- RangeTblEntry *resultRelRTE, *mergeRelRTE;
-
- /* There can't be any outer WITH to worry about */
- Assert(pstate->p_ctenamespace == NIL);
-
- qry->commandType = CMD_MERGE;
-
- /*
- * Check WHEN clauses for permissions and sanity
- */
- is_terminal[0] = false;
- is_terminal[1] = false;
- foreach(l, stmt->mergeActionList)
- {
- MergeAction *action = (MergeAction *) lfirst(l);
- uint when_type = (action->matched ? 0 : 1);
-
- /*
- * Collect action types so we can check Target permissions
- */
- switch (action->commandType)
- {
- case CMD_INSERT:
- {
- InsertStmt *istmt = (InsertStmt *) action->stmt;
- SelectStmt *selectStmt = (SelectStmt *) istmt->selectStmt;
-
- /*
- * The grammar allows attaching ORDER BY, LIMIT, FOR
- * UPDATE, or WITH to a VALUES clause and also multiple
- * VALUES clauses. If we have any of those, ERROR.
- */
- if (selectStmt && (selectStmt->valuesLists == NIL ||
- selectStmt->sortClause != NIL ||
- selectStmt->limitOffset != NULL ||
- selectStmt->limitCount != NULL ||
- selectStmt->lockingClause != NIL ||
- selectStmt->withClause != NULL))
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("SELECT not allowed in MERGE INSERT statement")));
-
- if (selectStmt && list_length(selectStmt->valuesLists) > 1)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("Multiple VALUES clauses not allowed in MERGE INSERT statement")));
-
- targetPerms |= ACL_INSERT;
- }
- break;
- case CMD_UPDATE:
- targetPerms |= ACL_UPDATE;
- break;
- case CMD_DELETE:
- targetPerms |= ACL_DELETE;
- break;
- case CMD_NOTHING:
- break;
- default:
- elog(ERROR, "unknown action in MERGE WHEN clause");
- }
-
- /*
- * Check for unreachable WHEN clauses
- */
- if (action->condition == NULL)
- is_terminal[when_type] = true;
- else if (is_terminal[when_type])
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("unreachable WHEN clause specified after unconditional WHEN clause")));
- }
-
- /*
- * Construct a query of the form
- * SELECT relation.ctid --junk attribute
- * ,relation.tableoid --junk attribute
- * ,source_relation.<somecols>
- * ,relation.<somecols>
- * FROM relation RIGHT JOIN source_relation
- * ON join_condition; -- no WHERE clause - all conditions are applied in
- * executor
- *
- * stmt->relation is the target relation, given as a RangeVar
- * stmt->source_relation is a RangeVar or subquery
- *
- * We specify the join as a RIGHT JOIN as a simple way of forcing the
- * first (larg) RTE to refer to the target table.
- *
- * The MERGE query's join can be tuned in some cases, see below for these
- * special case tweaks.
- *
- * We set QSRC_PARSER to show query constructed in parse analysis
- *
- * Note that we have only one Query for a MERGE statement and the planner
- * is called only once. That query is executed once to produce our stream
- * of candidate change rows, so the query must contain all of the columns
- * required by each of the targetlist or conditions for each action.
- *
- * As top-level statements INSERT, UPDATE and DELETE have a Query, whereas
- * with MERGE the individual actions do not require separate planning,
- * only different handling in the executor. See nodeModifyTable handling
- * of commandType CMD_MERGE.
- *
- * A sub-query can include the Target, but otherwise the sub-query cannot
- * reference the outermost Target table at all.
- */
- qry->querySource = QSRC_PARSER;
-
- /*
- * Setup the target table. Unlike regular UPDATE/DELETE, we don't expand
- * inheritance for the target relation in case of MERGE.
- *
- * This special arrangement is required for handling partitioned tables
- * because we perform an JOIN between the target and the source relation to
- * identify the matching and not-matching rows. If we take the usual path
- * of expanding the target table's inheritance and create one subplan per
- * partition, then we we won't be able to correctly identify the matching
- * and not-matching rows since for a given source row, there may not be a
- * matching row in one partition, but it may exists in some other
- * partition. So we must first append all the qualifying rows from all the
- * partitions and then do the matching.
- *
- * Once a target row is returned by the underlying join, we find the
- * correct partition and setup required state to carry out UPDATE/DELETE.
- * All of this happens during execution.
- */
- qry->resultRelation = setTargetTable(pstate, stmt->relation,
- false, /* do not expand inheritance */
- true, targetPerms);
-
- /*
- * Create a JOIN between the target and the source relation.
- */
- joinexpr = makeNode(JoinExpr);
- joinexpr->isNatural = false;
- joinexpr->alias = NULL;
- joinexpr->usingClause = NIL;
- joinexpr->quals = stmt->join_condition;
- joinexpr->larg = (Node *) stmt->relation;
- joinexpr->rarg = (Node *) stmt->source_relation;
-
- /*
- * Simplify the MERGE query as much as possible
- *
- * These seem like things that could go into Optimizer, but they are
- * semantic simplifications rather than optimizations, per se.
- *
- * If there are no INSERT actions we won't be using the non-matching
- * candidate rows for anything, so no need for an outer join. We do still
- * need an inner join for UPDATE and DELETE actions.
- */
- if (targetPerms & ACL_INSERT)
- joinexpr->jointype = JOIN_RIGHT;
- else
- joinexpr->jointype = JOIN_INNER;
-
- /*
- * We use a special purpose transformation here because the normal
- * routines don't quite work right for the MERGE case.
- *
- * A special mergeSourceTargetList is setup by transformMergeJoinClause().
- * It refers to all the attributes provided by the source relation. This
- * is later used by set_plan_refs() to fix the UPDATE/INSERT target lists
- * to so that they can correctly fetch the attributes from the source
- * relation.
- *
- * The target relation when used in the underlying join, gets a new RTE
- * with rte->inh set to true. We remember this RTE (and later pass on to
- * the planner and executor) for two main reasons:
- *
- * 1. If we ever need to run EvalPlanQual while performing MERGE, we must
- * make the modified tuple available to the underlying join query, which is
- * using a different RTE from the resultRelation RTE.
- *
- * 2. rewriteTargetListMerge() requires the RTE of the underlying join in
- * order to add junk CTID and TABLEOID attributes.
- */
- qry->mergeTarget_relation = transformMergeJoinClause(pstate, (Node *) joinexpr,
- &qry->mergeSourceTargetList);
-
- /*
- * The target table referenced in the MERGE is looked up twice; once while
- * setting it up as the result relation and again when it's used in the
- * underlying the join query. In some rare situations, it may happen that
- * these lookups return different results, for example, if a new relation
- * with the same name gets created in a schema which is ahead in the
- * search_path, in between the two lookups.
- *
- * It's a very narrow case, but nevertheless we guard against it by simply
- * checking if the OIDs returned by the two lookups is the same. If not, we
- * just throw an error.
- */
- Assert(qry->resultRelation > 0);
- Assert(qry->mergeTarget_relation > 0);
-
- /* Fetch both the RTEs */
- resultRelRTE = rt_fetch(qry->resultRelation, pstate->p_rtable);
- mergeRelRTE = rt_fetch(qry->mergeTarget_relation, pstate->p_rtable);
-
- if (resultRelRTE->relid != mergeRelRTE->relid)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("relation referenced by MERGE statement has changed")));
-
- /*
- * This query should just provide the source relation columns. Later, in
- * preprocess_targetlist(), we shall also add "ctid" attribute of the
- * target relation to ensure that the target tuple can be fetched
- * correctly.
- */
- qry->targetList = qry->mergeSourceTargetList;
-
- /* qry has no WHERE clause so absent quals are shown as NULL */
- qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
- qry->rtable = pstate->p_rtable;
-
- /*
- * XXX MERGE is unsupported in various cases
- */
- if (!(pstate->p_target_relation->rd_rel->relkind == RELKIND_RELATION ||
- pstate->p_target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("MERGE is not supported for this relation type")));
-
- if (pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
- pstate->p_target_relation->rd_rel->relhassubclass)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("MERGE is not supported for relations with inheritance")));
-
- if (pstate->p_target_relation->rd_rel->relhasrules)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("MERGE is not supported for relations with rules")));
-
- /*
- * We now have a good query shape, so now look at the when conditions and
- * action targetlists.
- *
- * Overall, the MERGE Query's targetlist is NIL.
- *
- * Each individual action has its own targetlist that needs separate
- * transformation. These transforms don't do anything to the overall
- * targetlist, since that is only used for resjunk columns.
- *
- * We can reference any column in Target or Source, which is OK because
- * both of those already have RTEs. There is nothing like the EXCLUDED
- * pseudo-relation for INSERT ON CONFLICT.
- */
- foreach(l, stmt->mergeActionList)
- {
- MergeAction *action = (MergeAction *) lfirst(l);
-
- /*
- * Set namespace for the specific action. This must be done before
- * analyzing the WHEN quals and the action targetlisst.
- */
- setNamespaceForMergeAction(pstate, action);
-
- /*
- * Transform the when condition.
- *
- * Note that these quals are NOT added to the join quals; instead they
- * are evaluated separately during execution to decide which of the
- * WHEN MATCHED or WHEN NOT MATCHED actions to execute.
- */
- action->qual = transformWhereClause(pstate, action->condition,
- EXPR_KIND_MERGE_WHEN_AND, "WHEN");
-
- /*
- * Transform target lists for each INSERT and UPDATE action stmt
- */
- switch (action->commandType)
- {
- case CMD_INSERT:
- {
- InsertStmt *istmt = (InsertStmt *) action->stmt;
- SelectStmt *selectStmt = (SelectStmt *) istmt->selectStmt;
- List *exprList = NIL;
- ListCell *lc;
- RangeTblEntry *rte;
- ListCell *icols;
- ListCell *attnos;
- List *icolumns;
- List *attrnos;
-
- pstate->p_is_insert = true;
-
- icolumns = checkInsertTargets(pstate, istmt->cols, &attrnos);
- Assert(list_length(icolumns) == list_length(attrnos));
-
- /*
- * Handle INSERT much like in transformInsertStmt
- */
- if (selectStmt == NULL)
- {
- /*
- * We have INSERT ... DEFAULT VALUES. We can handle
- * this case by emitting an empty targetlist --- all
- * columns will be defaulted when the planner expands
- * the targetlist.
- */
- exprList = NIL;
- }
- else
- {
- /*
- * Process INSERT ... VALUES with a single VALUES
- * sublist. We treat this case separately for
- * efficiency. The sublist is just computed directly
- * as the Query's targetlist, with no VALUES RTE. So
- * it works just like a SELECT without any FROM.
- */
- List *valuesLists = selectStmt->valuesLists;
-
- Assert(list_length(valuesLists) == 1);
- Assert(selectStmt->intoClause == NULL);
-
- /*
- * Do basic expression transformation (same as a ROW()
- * expr, but allow SetToDefault at top level)
- */
- exprList = transformExpressionList(pstate,
- (List *) linitial(valuesLists),
- EXPR_KIND_VALUES_SINGLE,
- true);
-
- /* Prepare row for assignment to target table */
- exprList = transformInsertRow(pstate, exprList,
- istmt->cols,
- icolumns, attrnos,
- false);
- }
-
- /*
- * Generate action's target list using the computed list
- * of expressions. Also, mark all the target columns as
- * needing insert permissions.
- */
- rte = pstate->p_target_rangetblentry;
- icols = list_head(icolumns);
- attnos = list_head(attrnos);
- foreach(lc, exprList)
- {
- Expr *expr = (Expr *) lfirst(lc);
- ResTarget *col;
- AttrNumber attr_num;
- TargetEntry *tle;
-
- col = lfirst_node(ResTarget, icols);
- attr_num = (AttrNumber) lfirst_int(attnos);
-
- tle = makeTargetEntry(expr,
- attr_num,
- col->name,
- false);
- action->targetList = lappend(action->targetList, tle);
-
- rte->insertedCols = bms_add_member(rte->insertedCols,
- attr_num - FirstLowInvalidHeapAttributeNumber);
-
- icols = lnext(icols);
- attnos = lnext(attnos);
- }
- }
- break;
- case CMD_UPDATE:
- {
- UpdateStmt *ustmt = (UpdateStmt *) action->stmt;
-
- pstate->p_is_insert = false;
- action->targetList = transformUpdateTargetList(pstate, ustmt->targetList);
- }
- break;
- case CMD_DELETE:
- break;
-
- case CMD_NOTHING:
- action->targetList = NIL;
- break;
- default:
- elog(ERROR, "unknown action in MERGE WHEN clause");
- }
- }
-
- qry->mergeActionList = stmt->mergeActionList;
-
- /* XXX maybe later */
- qry->returningList = NULL;
-
- qry->hasTargetSRFs = false;
- qry->hasSubLinks = pstate->p_hasSubLinks;
-
- assign_query_collations(pstate, qry);
-
- return qry;
-}
-
-static void
-setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte,
- bool rel_visible,
- bool cols_visible)
-{
- ListCell *lc;
-
- foreach(lc, namespace)
- {
- ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc);
-
- if (nsitem->p_rte == rte)
- {
- nsitem->p_rel_visible = rel_visible;
- nsitem->p_cols_visible = cols_visible;
- break;
- }
- }
-
-}
-
-/*
- * Expand the source relation to include all attributes of this RTE.
- *
- * This function is very similar to expandRelAttrs except that we don't mark
- * columns for SELECT privileges. That will be decided later when we transform
- * the action targetlists and the WHEN quals for actual references to the
- * source relation.
- */
-static List *
-expandSourceTL(ParseState *pstate, RangeTblEntry *rte, int rtindex)
-{
- List *names,
- *vars;
- ListCell *name,
- *var;
- List *te_list = NIL;
-
- expandRTE(rte, rtindex, 0, -1, false, &names, &vars);
-
- /*
- * Require read access to the table.
- */
- rte->requiredPerms |= ACL_SELECT;
-
- forboth(name, names, var, vars)
- {
- char *label = strVal(lfirst(name));
- Var *varnode = (Var *) lfirst(var);
- TargetEntry *te;
-
- te = makeTargetEntry((Expr *) varnode,
- (AttrNumber) pstate->p_next_resno++,
- label,
- false);
- te_list = lappend(te_list, te);
- }
-
- Assert(name == NULL && var == NULL); /* lists not the same length? */
-
- return te_list;
-}