Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer')
-rw-r--r--src/backend/optimizer/plan/createplan.c15
-rw-r--r--src/backend/optimizer/plan/planner.c64
-rw-r--r--src/backend/optimizer/plan/setrefs.c64
-rw-r--r--src/backend/optimizer/prep/prepjointree.c91
-rw-r--r--src/backend/optimizer/prep/preptlist.c37
-rw-r--r--src/backend/optimizer/util/appendinfo.c19
-rw-r--r--src/backend/optimizer/util/pathnode.c11
-rw-r--r--src/backend/optimizer/util/plancat.c4
8 files changed, 287 insertions, 18 deletions
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index fa069a217c8..179c87c6714 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -310,7 +310,8 @@ static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
List *resultRelations,
List *updateColnosLists,
List *withCheckOptionLists, List *returningLists,
- List *rowMarks, OnConflictExpr *onconflict, int epqParam);
+ List *rowMarks, OnConflictExpr *onconflict,
+ List *mergeActionList, int epqParam);
static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
GatherMergePath *best_path);
@@ -2775,6 +2776,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
best_path->returningLists,
best_path->rowMarks,
best_path->onconflict,
+ best_path->mergeActionLists,
best_path->epqParam);
copy_generic_path_info(&plan->plan, &best_path->path);
@@ -6924,7 +6926,8 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
List *resultRelations,
List *updateColnosLists,
List *withCheckOptionLists, List *returningLists,
- List *rowMarks, OnConflictExpr *onconflict, int epqParam)
+ List *rowMarks, OnConflictExpr *onconflict,
+ List *mergeActionLists, int epqParam)
{
ModifyTable *node = makeNode(ModifyTable);
List *fdw_private_list;
@@ -6932,9 +6935,10 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
ListCell *lc;
int i;
- Assert(operation == CMD_UPDATE ?
- list_length(resultRelations) == list_length(updateColnosLists) :
- updateColnosLists == NIL);
+ Assert(operation == CMD_MERGE ||
+ (operation == CMD_UPDATE ?
+ list_length(resultRelations) == list_length(updateColnosLists) :
+ updateColnosLists == NIL));
Assert(withCheckOptionLists == NIL ||
list_length(resultRelations) == list_length(withCheckOptionLists));
Assert(returningLists == NIL ||
@@ -6992,6 +6996,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
node->withCheckOptionLists = withCheckOptionLists;
node->returningLists = returningLists;
node->rowMarks = rowMarks;
+ node->mergeActionLists = mergeActionLists;
node->epqParam = epqParam;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd09f85aea1..547fda20a23 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -650,6 +650,11 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
SS_process_ctes(root);
/*
+ * If it's a MERGE command, transform the joinlist as appropriate.
+ */
+ transform_MERGE_to_join(parse);
+
+ /*
* If the FROM clause is empty, replace it with a dummy RTE_RESULT RTE, so
* that we don't need so many special cases to deal with that situation.
*/
@@ -849,6 +854,20 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
/* exclRelTlist contains only Vars, so no preprocessing needed */
}
+ foreach(l, parse->mergeActionList)
+ {
+ MergeAction *action = (MergeAction *) lfirst(l);
+
+ action->targetList = (List *)
+ preprocess_expression(root,
+ (Node *) action->targetList,
+ EXPRKIND_TARGET);
+ action->qual =
+ preprocess_expression(root,
+ (Node *) action->qual,
+ EXPRKIND_QUAL);
+ }
+
root->append_rel_list = (List *)
preprocess_expression(root, (Node *) root->append_rel_list,
EXPRKIND_APPINFO);
@@ -1714,7 +1733,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
}
/*
- * If this is an INSERT/UPDATE/DELETE, add the ModifyTable node.
+ * If this is an INSERT/UPDATE/DELETE/MERGE, add the ModifyTable node.
*/
if (parse->commandType != CMD_SELECT)
{
@@ -1723,6 +1742,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
List *updateColnosLists = NIL;
List *withCheckOptionLists = NIL;
List *returningLists = NIL;
+ List *mergeActionLists = NIL;
List *rowMarks;
if (bms_membership(root->all_result_relids) == BMS_MULTIPLE)
@@ -1789,6 +1809,43 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
returningLists = lappend(returningLists,
returningList);
}
+ if (parse->mergeActionList)
+ {
+ ListCell *l;
+ List *mergeActionList = NIL;
+
+ /*
+ * Copy MergeActions and translate stuff that
+ * references attribute numbers.
+ */
+ foreach(l, parse->mergeActionList)
+ {
+ MergeAction *action = lfirst(l),
+ *leaf_action = copyObject(action);
+
+ leaf_action->qual =
+ adjust_appendrel_attrs_multilevel(root,
+ (Node *) action->qual,
+ this_result_rel->relids,
+ top_result_rel->relids);
+ leaf_action->targetList = (List *)
+ adjust_appendrel_attrs_multilevel(root,
+ (Node *) action->targetList,
+ this_result_rel->relids,
+ top_result_rel->relids);
+ if (leaf_action->commandType == CMD_UPDATE)
+ leaf_action->updateColnos =
+ adjust_inherited_attnums_multilevel(root,
+ action->updateColnos,
+ this_result_rel->relid,
+ top_result_rel->relid);
+ mergeActionList = lappend(mergeActionList,
+ leaf_action);
+ }
+
+ mergeActionLists = lappend(mergeActionLists,
+ mergeActionList);
+ }
}
if (resultRelations == NIL)
@@ -1811,6 +1868,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
withCheckOptionLists = list_make1(parse->withCheckOptions);
if (parse->returningList)
returningLists = list_make1(parse->returningList);
+ if (parse->mergeActionList)
+ mergeActionLists = list_make1(parse->mergeActionList);
}
}
else
@@ -1823,6 +1882,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
withCheckOptionLists = list_make1(parse->withCheckOptions);
if (parse->returningList)
returningLists = list_make1(parse->returningList);
+ if (parse->mergeActionList)
+ mergeActionLists = list_make1(parse->mergeActionList);
}
/*
@@ -1859,6 +1920,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
returningLists,
rowMarks,
parse->onConflict,
+ mergeActionLists,
assign_special_exec_param(root));
}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index a7b11b7f03a..bf4c722c028 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -952,6 +952,7 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_ModifyTable:
{
ModifyTable *splan = (ModifyTable *) plan;
+ Plan *subplan = outerPlan(splan);
Assert(splan->plan.targetlist == NIL);
Assert(splan->plan.qual == NIL);
@@ -963,7 +964,6 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
if (splan->returningLists)
{
List *newRL = NIL;
- Plan *subplan = outerPlan(splan);
ListCell *lcrl,
*lcrr;
@@ -1030,6 +1030,68 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
fix_scan_list(root, splan->exclRelTlist, rtoffset, 1);
}
+ /*
+ * The MERGE statement produces the target rows by performing
+ * a right join between the target relation and the source
+ * relation (which could be a plain relation or a subquery).
+ * The INSERT and UPDATE actions of the MERGE statement
+ * require access to the columns from the source relation. We
+ * arrange things so that the source relation attributes are
+ * available as INNER_VAR and the target relation attributes
+ * are available from the scan tuple.
+ */
+ if (splan->mergeActionLists != NIL)
+ {
+ ListCell *lca,
+ *lcr;
+
+ /*
+ * Fix the targetList of individual action nodes so that
+ * the so-called "source relation" Vars are referenced as
+ * INNER_VAR. Note that for this to work correctly during
+ * execution, the ecxt_innertuple must be set to the tuple
+ * obtained by executing the subplan, which is what
+ * constitutes the "source relation".
+ *
+ * We leave the Vars from the result relation (i.e. the
+ * target relation) unchanged i.e. those Vars would be
+ * picked from the scan slot. So during execution, we must
+ * ensure that ecxt_scantuple is setup correctly to refer
+ * to the tuple from the target relation.
+ */
+ indexed_tlist *itlist;
+
+ itlist = build_tlist_index(subplan->targetlist);
+
+ forboth(lca, splan->mergeActionLists,
+ lcr, splan->resultRelations)
+ {
+ List *mergeActionList = lfirst(lca);
+ Index resultrel = lfirst_int(lcr);
+
+ foreach(l, mergeActionList)
+ {
+ MergeAction *action = (MergeAction *) lfirst(l);
+
+ /* Fix targetList of each action. */
+ action->targetList = fix_join_expr(root,
+ action->targetList,
+ NULL, itlist,
+ resultrel,
+ rtoffset,
+ NUM_EXEC_TLIST(plan));
+
+ /* Fix quals too. */
+ action->qual = (Node *) fix_join_expr(root,
+ (List *) action->qual,
+ NULL, itlist,
+ resultrel,
+ rtoffset,
+ NUM_EXEC_QUAL(plan));
+ }
+ }
+ }
+
splan->nominalRelation += rtoffset;
if (splan->rootRelation)
splan->rootRelation += rtoffset;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 74823e8437a..0bd99acf836 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -133,6 +133,86 @@ static Node *find_jointree_node_for_rel(Node *jtnode, int relid);
/*
+ * transform_MERGE_to_join
+ * Replace a MERGE's jointree to also include the target relation.
+ */
+void
+transform_MERGE_to_join(Query *parse)
+{
+ RangeTblEntry *joinrte;
+ JoinExpr *joinexpr;
+ JoinType jointype;
+ int joinrti;
+ List *vars;
+
+ if (parse->commandType != CMD_MERGE)
+ return;
+
+ /* XXX probably bogus */
+ vars = NIL;
+
+ /*
+ * When any WHEN NOT MATCHED THEN INSERT clauses exist, we need to use an
+ * outer join so that we process all unmatched tuples from the source
+ * relation. If none exist, we can use an inner join.
+ */
+ if (parse->mergeUseOuterJoin)
+ jointype = JOIN_RIGHT;
+ else
+ jointype = JOIN_INNER;
+
+ /* Manufacture a join RTE to use. */
+ joinrte = makeNode(RangeTblEntry);
+ joinrte->rtekind = RTE_JOIN;
+ joinrte->jointype = jointype;
+ joinrte->joinmergedcols = 0;
+ joinrte->joinaliasvars = vars;
+ joinrte->joinleftcols = NIL; /* MERGE does not allow JOIN USING */
+ joinrte->joinrightcols = NIL; /* ditto */
+ joinrte->join_using_alias = NULL;
+
+ joinrte->alias = NULL;
+ joinrte->eref = makeAlias("*MERGE*", NIL);
+ joinrte->lateral = false;
+ joinrte->inh = false;
+ joinrte->inFromCl = true;
+ joinrte->requiredPerms = 0;
+ joinrte->checkAsUser = InvalidOid;
+ joinrte->selectedCols = NULL;
+ joinrte->insertedCols = NULL;
+ joinrte->updatedCols = NULL;
+ joinrte->extraUpdatedCols = NULL;
+ joinrte->securityQuals = NIL;
+
+ /*
+ * Add completed RTE to pstate's range table list, so that we know its
+ * index.
+ */
+ parse->rtable = lappend(parse->rtable, joinrte);
+ joinrti = list_length(parse->rtable);
+
+ /*
+ * Create a JOIN between the target and the source relation.
+ */
+ joinexpr = makeNode(JoinExpr);
+ joinexpr->jointype = jointype;
+ joinexpr->isNatural = false;
+ joinexpr->larg = (Node *) makeNode(RangeTblRef);
+ ((RangeTblRef *) joinexpr->larg)->rtindex = parse->resultRelation;
+ joinexpr->rarg = linitial(parse->jointree->fromlist); /* original join */
+ joinexpr->usingClause = NIL;
+ joinexpr->join_using_alias = NULL;
+ /* The quals are removed from the jointree and into this specific join */
+ joinexpr->quals = parse->jointree->quals;
+ joinexpr->alias = NULL;
+ joinexpr->rtindex = joinrti;
+
+ /* Make the new join be the sole entry in the query's jointree */
+ parse->jointree->fromlist = list_make1(joinexpr);
+ parse->jointree->quals = NULL;
+}
+
+/*
* replace_empty_jointree
* If the Query's jointree is empty, replace it with a dummy RTE_RESULT
* relation.
@@ -2058,6 +2138,17 @@ perform_pullup_replace_vars(PlannerInfo *root,
* can't contain any references to a subquery.
*/
}
+ if (parse->mergeActionList)
+ {
+ foreach(lc, parse->mergeActionList)
+ {
+ MergeAction *action = lfirst(lc);
+
+ action->qual = pullup_replace_vars(action->qual, rvcontext);
+ action->targetList = (List *)
+ pullup_replace_vars((Node *) action->targetList, rvcontext);
+ }
+ }
replace_vars_in_jointree((Node *) parse->jointree, rvcontext,
lowest_nulling_outer_join);
Assert(parse->setOperations == NULL);
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 95e82cf958f..99ab3d75594 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -125,6 +125,43 @@ preprocess_targetlist(PlannerInfo *root)
}
/*
+ * For MERGE we need to handle the target list for the target relation,
+ * and also target list for each action (only INSERT/UPDATE matter).
+ */
+ if (command_type == CMD_MERGE)
+ {
+ ListCell *l;
+
+ /*
+ * For MERGE, add any junk column(s) needed to allow the executor to
+ * identify the rows to be inserted or updated.
+ */
+ root->processed_tlist = tlist;
+ add_row_identity_columns(root, result_relation,
+ target_rte, target_relation);
+
+ tlist = root->processed_tlist;
+
+ /*
+ * For MERGE, handle targetlist of each MergeAction separately. Give
+ * the same treatment to MergeAction->targetList as we would have
+ * given to a regular INSERT. For UPDATE, collect the column numbers
+ * being modified.
+ */
+ foreach(l, parse->mergeActionList)
+ {
+ MergeAction *action = (MergeAction *) lfirst(l);
+
+ if (action->commandType == CMD_INSERT)
+ action->targetList = expand_insert_targetlist(action->targetList,
+ target_relation);
+ else if (action->commandType == CMD_UPDATE)
+ action->updateColnos =
+ extract_update_targetlist_colnos(action->targetList);
+ }
+ }
+
+ /*
* Add necessary junk columns for rowmarked rels. These values are needed
* for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual
* rechecking. See comments for PlanRowMark in plannodes.h. If you
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 2f06fa743c2..9d4bb470270 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -774,8 +774,8 @@ add_row_identity_var(PlannerInfo *root, Var *orig_var,
Assert(orig_var->varlevelsup == 0);
/*
- * If we're doing non-inherited UPDATE/DELETE, there's little need for
- * ROWID_VAR shenanigans. Just shove the presented Var into the
+ * If we're doing non-inherited UPDATE/DELETE/MERGE, there's little need
+ * for ROWID_VAR shenanigans. Just shove the presented Var into the
* processed_tlist, and we're done.
*/
if (rtindex == root->parse->resultRelation)
@@ -862,14 +862,16 @@ add_row_identity_columns(PlannerInfo *root, Index rtindex,
char relkind = target_relation->rd_rel->relkind;
Var *var;
- Assert(commandType == CMD_UPDATE || commandType == CMD_DELETE);
+ Assert(commandType == CMD_UPDATE || commandType == CMD_DELETE || commandType == CMD_MERGE);
- if (relkind == RELKIND_RELATION ||
+ if (commandType == CMD_MERGE ||
+ relkind == RELKIND_RELATION ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_PARTITIONED_TABLE)
{
/*
- * Emit CTID so that executor can find the row to update or delete.
+ * Emit CTID so that executor can find the row to merge, update or
+ * delete.
*/
var = makeVar(rtindex,
SelfItemPointerAttributeNumber,
@@ -942,8 +944,11 @@ distribute_row_identity_vars(PlannerInfo *root)
RelOptInfo *target_rel;
ListCell *lc;
- /* There's nothing to do if this isn't an inherited UPDATE/DELETE. */
- if (parse->commandType != CMD_UPDATE && parse->commandType != CMD_DELETE)
+ /*
+ * There's nothing to do if this isn't an inherited UPDATE/DELETE/MERGE.
+ */
+ if (parse->commandType != CMD_UPDATE && parse->commandType != CMD_DELETE &&
+ parse->commandType != CMD_MERGE)
{
Assert(root->row_identity_vars == NIL);
return;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 5c32c96b71c..99df76b6b71 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3620,6 +3620,7 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
* 'rowMarks' is a list of PlanRowMarks (non-locking only)
* 'onconflict' is the ON CONFLICT clause, or NULL
* 'epqParam' is the ID of Param for EvalPlanQual re-eval
+ * 'mergeActionLists' is a list of lists of MERGE actions (one per rel)
*/
ModifyTablePath *
create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
@@ -3631,13 +3632,14 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
List *updateColnosLists,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict,
- int epqParam)
+ List *mergeActionLists, int epqParam)
{
ModifyTablePath *pathnode = makeNode(ModifyTablePath);
- Assert(operation == CMD_UPDATE ?
- list_length(resultRelations) == list_length(updateColnosLists) :
- updateColnosLists == NIL);
+ Assert(operation == CMD_MERGE ||
+ (operation == CMD_UPDATE ?
+ list_length(resultRelations) == list_length(updateColnosLists) :
+ updateColnosLists == NIL));
Assert(withCheckOptionLists == NIL ||
list_length(resultRelations) == list_length(withCheckOptionLists));
Assert(returningLists == NIL ||
@@ -3697,6 +3699,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->rowMarks = rowMarks;
pathnode->onconflict = onconflict;
pathnode->epqParam = epqParam;
+ pathnode->mergeActionLists = mergeActionLists;
return pathnode;
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a5002ad8955..df97b799174 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -2167,6 +2167,10 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
trigDesc->trig_delete_before_row))
result = true;
break;
+ /* There is no separate event for MERGE, only INSERT/UPDATE/DELETE */
+ case CMD_MERGE:
+ result = false;
+ break;
default:
elog(ERROR, "unrecognized CmdType: %d", (int) event);
break;