diff options
Diffstat (limited to 'src/backend/optimizer/prep/prepjointree.c')
-rw-r--r-- | src/backend/optimizer/prep/prepjointree.c | 66 |
1 files changed, 61 insertions, 5 deletions
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 4195a0a84fe..01239c0cb04 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -158,6 +158,9 @@ transform_MERGE_to_join(Query *parse) int joinrti; List *vars; RangeTblRef *rtr; + FromExpr *target; + Node *source; + int sourcerti; if (parse->commandType != CMD_MERGE) return; @@ -226,13 +229,36 @@ transform_MERGE_to_join(Query *parse) * parse->jointree->quals are restrictions on the target relation (if the * target relation is an auto-updatable view). */ + /* target rel, with any quals */ rtr = makeNode(RangeTblRef); rtr->rtindex = parse->mergeTargetRelation; + target = makeFromExpr(list_make1(rtr), parse->jointree->quals); + + /* source rel (expect exactly one -- see transformMergeStmt()) */ + Assert(list_length(parse->jointree->fromlist) == 1); + source = linitial(parse->jointree->fromlist); + + /* + * index of source rel (expect either a RangeTblRef or a JoinExpr -- see + * transformFromClauseItem()). + */ + if (IsA(source, RangeTblRef)) + sourcerti = ((RangeTblRef *) source)->rtindex; + else if (IsA(source, JoinExpr)) + sourcerti = ((JoinExpr *) source)->rtindex; + else + { + elog(ERROR, "unrecognized source node type: %d", + (int) nodeTag(source)); + sourcerti = 0; /* keep compiler quiet */ + } + + /* Join the source and target */ joinexpr = makeNode(JoinExpr); joinexpr->jointype = jointype; joinexpr->isNatural = false; - joinexpr->larg = (Node *) makeFromExpr(list_make1(rtr), parse->jointree->quals); - joinexpr->rarg = linitial(parse->jointree->fromlist); /* source rel */ + joinexpr->larg = (Node *) target; + joinexpr->rarg = source; joinexpr->usingClause = NIL; joinexpr->join_using_alias = NULL; joinexpr->quals = parse->mergeJoinCondition; @@ -261,9 +287,39 @@ transform_MERGE_to_join(Query *parse) * use the join condition to distinguish between MATCHED and NOT MATCHED * BY SOURCE cases. Otherwise, it's no longer needed, and we set it to * NULL, saving cycles during planning and execution. - */ - if (!have_action[MERGE_WHEN_NOT_MATCHED_BY_SOURCE]) - parse->mergeJoinCondition = NULL; + * + * We need to be careful though: the executor evaluates this condition + * using the output of the join subplan node, which nulls the output from + * the source relation when the join condition doesn't match. That risks + * producing incorrect results when rechecking using a "non-strict" join + * condition, such as "src.col IS NOT DISTINCT FROM tgt.col". To guard + * against that, we add an additional "src IS NOT NULL" check to the join + * condition, so that it does the right thing when performing a recheck + * based on the output of the join subplan. + */ + if (have_action[MERGE_WHEN_NOT_MATCHED_BY_SOURCE]) + { + Var *var; + NullTest *ntest; + + /* source wholerow Var (nullable by the new join) */ + var = makeWholeRowVar(rt_fetch(sourcerti, parse->rtable), + sourcerti, 0, false); + var->varnullingrels = bms_make_singleton(joinrti); + + /* "src IS NOT NULL" check */ + ntest = makeNode(NullTest); + ntest->arg = (Expr *) var; + ntest->nulltesttype = IS_NOT_NULL; + ntest->argisrow = false; + ntest->location = -1; + + /* combine it with the original join condition */ + parse->mergeJoinCondition = + (Node *) make_and_qual((Node *) ntest, parse->mergeJoinCondition); + } + else + parse->mergeJoinCondition = NULL; /* join condition not needed */ } /* |