mirror of
https://github.com/postgres/postgres.git
synced 2025-08-27 07:42:10 +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:
@@ -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;
|
||||
|
||||
/*
|
||||
|
@@ -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));
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -132,6 +132,86 @@ static void fix_append_rel_relids(List *append_rel_list, int varno,
|
||||
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
|
||||
@@ -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);
|
||||
|
@@ -124,6 +124,43 @@ preprocess_targetlist(PlannerInfo *root)
|
||||
tlist = root->processed_tlist;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user