diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/executor/nodeMerge.c | 575 | ||||
-rw-r--r-- | src/backend/parser/parse_merge.c | 660 |
2 files changed, 1235 insertions, 0 deletions
diff --git a/src/backend/executor/nodeMerge.c b/src/backend/executor/nodeMerge.c new file mode 100644 index 00000000000..0e0d0795d4d --- /dev/null +++ b/src/backend/executor/nodeMerge.c @@ -0,0 +1,575 @@ +/*------------------------------------------------------------------------- + * + * 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 new file mode 100644 index 00000000000..e073d93daf6 --- /dev/null +++ b/src/backend/parser/parse_merge.c @@ -0,0 +1,660 @@ +/*------------------------------------------------------------------------- + * + * 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; +} |