Skip to content

Commit 38ad5ba

Browse files
committed
Support distribution key update in MERGE for partitioned tables
Previously, MERGE ... WHEN MATCHED THEN UPDATE SET <dist_key> = ... was rejected with "cannot update column in merge with distributed column". This commit adds support by extending the SplitMerge node to handle distribution key updates via DELETE + INSERT routing. Key changes: Planner (preptlist.c, createplan.c, setrefs.c, cdbpath.c): - Detect distribution key modification in MERGE UPDATE actions and set merge_need_split_update flag - Add all target table columns to subplan targetlist so SplitMerge can project complete rows for INSERT - Expand UPDATE action targetlists to include all columns (not just SET columns) using expand_insert_targetlist - Build SplitMerge targetlist in N+M+1 format: N target table columns + M subplan columns + 1 DMLAction column - Always use root table action lists (not per-partition adjusted lists) to ensure hashAttnos match root attribute numbers - Add set_splitmerge_tlist_references for proper OUTER_VAR conversion Executor (nodeSplitMerge.c, nodeModifyTable.c): - SplitMerge splits MATCHED UPDATE into DELETE + INSERT tuple pair, each routed to the correct segment via hash computation - NOT MATCHED rows get PASSTHROUGH action for normal ExecMerge processing - ModifyTable handles DMLAction-tagged tuples from SplitMerge output - Support lazy partition routing for CMD_MERGE DML_INSERT Refactoring (nodeSplitMerge.c): - Extract computeTargetSegment(), SwitchResultRelForPartition(), BuildRootUpdateTupleDesc() helper functions - Define SPLITMERGE_ACTION_PASSTHROUGH constant - Remove dead code and consolidate duplicated logic
1 parent 7e96c27 commit 38ad5ba

18 files changed

Lines changed: 1201 additions & 228 deletions

File tree

contrib/pax_storage/src/test/regress/expected/privileges.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1018,7 +1018,7 @@ WHEN MATCHED THEN
10181018
UPDATE SET b = s.b, a = t.a + 1
10191019
WHEN NOT MATCHED THEN
10201020
INSERT VALUES (a, b);
1021-
ERROR: cannot update column in merge with distributed column
1021+
ERROR: permission denied for table mtarget
10221022
-- fail (no SELECT on t.b)
10231023
MERGE INTO mtarget t USING msource s ON t.a = s.a
10241024
WHEN MATCHED AND t.b IS NOT NULL THEN

src/backend/cdb/cdbpath.c

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ static bool try_redistribute(PlannerInfo *root, CdbpathMfjRel *g,
7575

7676
static SplitUpdatePath *make_splitupdate_path(PlannerInfo *root, Path *subpath, Index rti);
7777

78-
static SplitMergePath *make_split_merge_path(PlannerInfo *root, Path *subpath, List* resultRelations, List *mergeActionLists);
78+
static SplitMergePath *make_split_merge_path(PlannerInfo *root, Path *subpath, List* resultRelations, List *mergeActionLists, bool hasSplitUpdate);
7979

8080
static bool can_elide_explicit_motion(PlannerInfo *root, Index rti, Path *subpath, GpPolicy *policy);
8181
/*
@@ -2798,19 +2798,35 @@ create_motion_path_for_merge(PlannerInfo *root, List *resultRelations, GpPolicy
27982798
{
27992799
/*
28002800
* If merge contain CMD_INSERT, we need split merge to let new
2801-
* insert tuple redistributed to correct segment. otherwise, we
2802-
* create motion as the same as update/delete in create_motion_path_for_upddel
2801+
* insert tuple redistributed to correct segment. If merge has
2802+
* UPDATE that modifies distribution key, we also need split merge
2803+
* to handle the DELETE+INSERT split.
28032804
*/
28042805
foreach(l, mergeActionLists)
28052806
{
28062807
List *mergeActionList = lfirst(l);
2807-
foreach(lc, mergeActionList)
2808+
foreach(lc, mergeActionList)
28082809
{
28092810
MergeAction *action = lfirst(lc);
28102811
if (action->commandType == CMD_INSERT)
28112812
need_split_merge = true;
28122813
}
28132814
}
2815+
if (root->merge_need_split_update)
2816+
{
2817+
need_split_merge = true;
2818+
2819+
/*
2820+
* Same restriction as SplitUpdate: updating a distribution key
2821+
* is not allowed when the target relation has update triggers.
2822+
*/
2823+
RangeTblEntry *rte = rt_fetch(root->parse->resultRelation,
2824+
root->parse->rtable);
2825+
if (has_update_triggers(rte->relid, true))
2826+
ereport(ERROR,
2827+
(errcode(ERRCODE_GP_FEATURE_NOT_YET),
2828+
errmsg("UPDATE on distributed key column not allowed on relation with update triggers")));
2829+
}
28142830

28152831
if (need_split_merge)
28162832
{
@@ -2820,7 +2836,7 @@ create_motion_path_for_merge(PlannerInfo *root, List *resultRelations, GpPolicy
28202836
rel = build_simple_rel(root, linitial_int(resultRelations), NULL /*parent*/);
28212837
targetLocus = cdbpathlocus_from_baserel(root, rel, 0);
28222838

2823-
subpath = (Path *) make_split_merge_path(root, subpath, resultRelations, mergeActionLists);
2839+
subpath = (Path *) make_split_merge_path(root, subpath, resultRelations, mergeActionLists, root->merge_need_split_update);
28242840
subpath = cdbpath_create_explicit_motion_path(root,
28252841
subpath,
28262842
targetLocus);
@@ -2923,14 +2939,21 @@ turn_volatile_seggen_to_singleqe(PlannerInfo *root, Path *path, Node *node)
29232939
}
29242940

29252941
static SplitMergePath *
2926-
make_split_merge_path(PlannerInfo *root, Path *subpath, List *resultRelations, List *mergeActionLists)
2942+
make_split_merge_path(PlannerInfo *root, Path *subpath, List *resultRelations, List *mergeActionLists, bool hasSplitUpdate)
29272943
{
29282944
PathTarget *splitMergePathTarget;
29292945
SplitMergePath *splitmergepath;
29302946

29312947
splitMergePathTarget = copy_pathtarget(subpath->pathtarget);
29322948

2933-
/* populate information generated above into splitupdate node */
2949+
/* When hasSplitUpdate, add DMLAction column for split UPDATE handling */
2950+
if (hasSplitUpdate)
2951+
{
2952+
DMLActionExpr *actionExpr = makeNode(DMLActionExpr);
2953+
add_column_to_pathtarget(splitMergePathTarget, (Expr *) actionExpr, 0);
2954+
}
2955+
2956+
/* populate information generated above into splitmerge node */
29342957
splitmergepath = makeNode(SplitMergePath);
29352958
splitmergepath->path.pathtype = T_SplitMerge;
29362959
splitmergepath->path.parent = subpath->parent;
@@ -2947,6 +2970,7 @@ make_split_merge_path(PlannerInfo *root, Path *subpath, List *resultRelations, L
29472970
splitmergepath->subpath = subpath;
29482971
splitmergepath->resultRelations = resultRelations;
29492972
splitmergepath->mergeActionLists = mergeActionLists;
2973+
splitmergepath->hasSplitUpdate = hasSplitUpdate;
29502974

29512975
return splitmergepath;
29522976
}

src/backend/executor/nodeModifyTable.c

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,7 +1095,7 @@ ExecInsert(ModifyTableContext *context,
10951095
*/
10961096
if (mtstate->operation == CMD_UPDATE)
10971097
wco_kind = WCO_RLS_UPDATE_CHECK;
1098-
else if (mtstate->operation == CMD_MERGE)
1098+
else if (mtstate->operation == CMD_MERGE && context->relaction != NULL)
10991099
wco_kind = (context->relaction->mas_action->commandType == CMD_UPDATE) ?
11001100
WCO_RLS_UPDATE_CHECK : WCO_RLS_INSERT_CHECK;
11011101
else
@@ -4372,7 +4372,77 @@ ExecModifyTable(PlanState *pstate)
43724372
break;
43734373

43744374
case CMD_MERGE:
4375-
slot = ExecMerge(&context, resultRelInfo, tupleid, node->canSetTag);
4375+
if (action == (int) DML_INSERT)
4376+
{
4377+
/*
4378+
* Split merge INSERT: extract non-junk columns (positions
4379+
* 1..N) from the plan slot into a properly-typed insert
4380+
* slot. We can't use ExecGetInsertNewTuple because the
4381+
* SplitMerge's ExecInitMergeTupleSlots may have set
4382+
* ri_projectNewInfoValid without building the INSERT
4383+
* projection.
4384+
*/
4385+
ResultRelInfo *saved = resultRelInfo;
4386+
TupleTableSlot *insertSlot;
4387+
int natts;
4388+
4389+
resultRelInfo = node->rootResultRelInfo;
4390+
natts = RelationGetNumberOfAttributes(resultRelInfo->ri_RelationDesc);
4391+
4392+
/*
4393+
* Lazily set up partition tuple routing for split-update
4394+
* MERGE INSERT on partitioned tables.
4395+
*/
4396+
if (resultRelInfo->ri_RelationDesc->rd_rel->relkind ==
4397+
RELKIND_PARTITIONED_TABLE &&
4398+
node->mt_partition_tuple_routing == NULL)
4399+
{
4400+
node->mt_partition_tuple_routing =
4401+
ExecSetupPartitionTupleRouting(estate,
4402+
resultRelInfo->ri_RelationDesc);
4403+
}
4404+
4405+
insertSlot = resultRelInfo->ri_newTupleSlot;
4406+
if (insertSlot == NULL)
4407+
{
4408+
insertSlot = table_slot_create(resultRelInfo->ri_RelationDesc,
4409+
&estate->es_tupleTable);
4410+
resultRelInfo->ri_newTupleSlot = insertSlot;
4411+
}
4412+
4413+
ExecClearTuple(insertSlot);
4414+
slot_getallattrs(context.planSlot);
4415+
memcpy(insertSlot->tts_values, context.planSlot->tts_values,
4416+
natts * sizeof(Datum));
4417+
memcpy(insertSlot->tts_isnull, context.planSlot->tts_isnull,
4418+
natts * sizeof(bool));
4419+
ExecStoreVirtualTuple(insertSlot);
4420+
4421+
/* Set relaction to NULL to avoid ExecInsert dereferencing it */
4422+
context.relaction = NULL;
4423+
4424+
slot = ExecInsert(&context, resultRelInfo, insertSlot,
4425+
node->canSetTag, NULL, NULL,
4426+
true /* splitUpdate */);
4427+
resultRelInfo = saved;
4428+
}
4429+
else if (action == (int) DML_DELETE)
4430+
{
4431+
/*
4432+
* Split merge DELETE: delete the old tuple on this segment.
4433+
*/
4434+
slot = ExecDelete(&context, resultRelInfo, tupleid, oldtuple, segid,
4435+
false, /* processReturning */
4436+
false, /* changingPart */
4437+
node->canSetTag,
4438+
NULL, NULL, NULL,
4439+
true /* splitUpdate */);
4440+
}
4441+
else
4442+
{
4443+
/* Normal MERGE processing (no split or pass-through) */
4444+
slot = ExecMerge(&context, resultRelInfo, tupleid, node->canSetTag);
4445+
}
43764446
break;
43774447

43784448
default:
@@ -4661,6 +4731,11 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
46614731
mtstate->mt_action_attno =
46624732
ExecFindJunkAttributeInTlist(subplan->targetlist, "DMLAction");
46634733
}
4734+
else if (operation == CMD_MERGE)
4735+
{
4736+
mtstate->mt_action_attno =
4737+
ExecFindJunkAttributeInTlist(subplan->targetlist, "DMLAction");
4738+
}
46644739
}
46654740
/*
46664741
* Do additional per-result-relation initialization.

0 commit comments

Comments
 (0)