1
0
mirror of https://github.com/postgres/postgres.git synced 2025-09-02 04:21:28 +03:00

Add support for MERGE SQL command

MERGE performs actions that modify rows in the target table using a
source table or query. MERGE provides a single SQL statement that can
conditionally INSERT/UPDATE/DELETE rows -- a task that would otherwise
require multiple PL statements.  For example,

MERGE INTO target AS t
USING source AS s
ON t.tid = s.sid
WHEN MATCHED AND t.balance > s.delta THEN
  UPDATE SET balance = t.balance - s.delta
WHEN MATCHED THEN
  DELETE
WHEN NOT MATCHED AND s.delta > 0 THEN
  INSERT VALUES (s.sid, s.delta)
WHEN NOT MATCHED THEN
  DO NOTHING;

MERGE works with regular tables, partitioned tables and inheritance
hierarchies, including column and row security enforcement, as well as
support for row and statement triggers and transition tables therein.

MERGE is optimized for OLTP and is parameterizable, though also useful
for large scale ETL/ELT. MERGE is not intended to be used in preference
to existing single SQL commands for INSERT, UPDATE or DELETE since there
is some overhead.  MERGE can be used from PL/pgSQL.

MERGE does not support targetting updatable views or foreign tables, and
RETURNING clauses are not allowed either.  These limitations are likely
fixable with sufficient effort.  Rewrite rules are also not supported,
but it's not clear that we'd want to support them.

Author: Pavan Deolasee <pavan.deolasee@gmail.com>
Author: Álvaro Herrera <alvherre@alvh.no-ip.org>
Author: Amit Langote <amitlangote09@gmail.com>
Author: Simon Riggs <simon.riggs@enterprisedb.com>
Reviewed-by: Peter Eisentraut <peter.eisentraut@enterprisedb.com>
Reviewed-by: Andres Freund <andres@anarazel.de> (earlier versions)
Reviewed-by: Peter Geoghegan <pg@bowt.ie> (earlier versions)
Reviewed-by: Robert Haas <robertmhaas@gmail.com> (earlier versions)
Reviewed-by: Japin Li <japinli@hotmail.com>
Reviewed-by: Justin Pryzby <pryzby@telsasoft.com>
Reviewed-by: Tomas Vondra <tomas.vondra@enterprisedb.com>
Reviewed-by: Zhihong Yu <zyu@yugabyte.com>
Discussion: https://postgr.es/m/CANP8+jKitBSrB7oTgT9CY2i1ObfOt36z0XMraQc+Xrz8QB0nXA@mail.gmail.com
Discussion: https://postgr.es/m/CAH2-WzkJdBuxj9PO=2QaO9-3h3xGbQPZ34kJH=HukRekwM-GZg@mail.gmail.com
Discussion: https://postgr.es/m/20201231134736.GA25392@alvherre.pgsql
This commit is contained in:
Alvaro Herrera
2022-03-28 16:45:58 +02:00
parent ae63017bdb
commit 7103ebb7aa
95 changed files with 8726 additions and 167 deletions

View File

@@ -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;
/*

View File

@@ -649,6 +649,11 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
if (parse->cteList)
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));
}

View File

@@ -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;