Skip to content

Commit bf2b205

Browse files
committed
ExecMerge
1 parent 9d93c83 commit bf2b205

File tree

1 file changed

+110
-80
lines changed

1 file changed

+110
-80
lines changed

src/nodes/modify_hypertable_exec.c

Lines changed: 110 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2275,6 +2275,116 @@ ExecOnConflictUpdate(ModifyTableContext *context,
22752275
return true;
22762276
}
22772277

2278+
/*
2279+
* Perform MERGE.
2280+
*/
2281+
static TupleTableSlot *
2282+
ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ChunkDispatchState *cds,
2283+
ItemPointer tupleid, HeapTuple oldtuple, bool canSetTag)
2284+
{
2285+
TupleTableSlot *rslot = NULL;
2286+
bool matched;
2287+
2288+
/*-----
2289+
* If we are dealing with a WHEN MATCHED case, tupleid or oldtuple is
2290+
* valid, depending on whether the result relation is a table or a view.
2291+
* We execute the first action for which the additional WHEN MATCHED AND
2292+
* quals pass. If an action without quals is found, that action is
2293+
* executed.
2294+
*
2295+
* Similarly, in the WHEN NOT MATCHED BY SOURCE case, tupleid or oldtuple
2296+
* is valid, and we look at the given WHEN NOT MATCHED BY SOURCE actions
2297+
* in sequence until one passes. This is almost identical to the WHEN
2298+
* MATCHED case, and both cases are handled by ExecMergeMatched().
2299+
*
2300+
* Finally, in the WHEN NOT MATCHED [BY TARGET] case, both tupleid and
2301+
* oldtuple are invalid, and we look at the given WHEN NOT MATCHED [BY
2302+
* TARGET] actions in sequence until one passes.
2303+
*
2304+
* Things get interesting in case of concurrent update/delete of the
2305+
* target tuple. Such concurrent update/delete is detected while we are
2306+
* executing a WHEN MATCHED or WHEN NOT MATCHED BY SOURCE action.
2307+
*
2308+
* A concurrent update can:
2309+
*
2310+
* 1. modify the target tuple so that the results from checking any
2311+
* additional quals attached to WHEN MATCHED or WHEN NOT MATCHED BY
2312+
* SOURCE actions potentially change, but the result from the join
2313+
* quals does not change.
2314+
*
2315+
* In this case, we are still dealing with the same kind of match
2316+
* (MATCHED or NOT MATCHED BY SOURCE). We recheck the same list of
2317+
* actions from the start and choose the first one that satisfies the
2318+
* new target tuple.
2319+
*
2320+
* 2. modify the target tuple in the WHEN MATCHED case so that the join
2321+
* quals no longer pass and hence the source and target tuples no
2322+
* longer match.
2323+
*
2324+
* In this case, we are now dealing with a NOT MATCHED case, and we
2325+
* process both WHEN NOT MATCHED BY SOURCE and WHEN NOT MATCHED [BY
2326+
* TARGET] actions. First ExecMergeMatched() processes the list of
2327+
* WHEN NOT MATCHED BY SOURCE actions in sequence until one passes,
2328+
* then ExecMergeNotMatched() processes any WHEN NOT MATCHED [BY
2329+
* TARGET] actions in sequence until one passes. Thus we may execute
2330+
* two actions; one of each kind.
2331+
*
2332+
* Thus we support concurrent updates that turn MATCHED candidate rows
2333+
* into NOT MATCHED rows. However, we do not attempt to support cases
2334+
* that would turn NOT MATCHED rows into MATCHED rows, or which would
2335+
* cause a target row to match a different source row.
2336+
*
2337+
* A concurrent delete changes a WHEN MATCHED case to WHEN NOT MATCHED
2338+
* [BY TARGET].
2339+
*
2340+
* ExecMergeMatched() takes care of following the update chain and
2341+
* re-finding the qualifying WHEN MATCHED or WHEN NOT MATCHED BY SOURCE
2342+
* action, as long as the target tuple still exists. If the target tuple
2343+
* gets deleted or a concurrent update causes the join quals to fail, it
2344+
* returns a matched status of false and we call ExecMergeNotMatched().
2345+
* Given that ExecMergeMatched() always makes progress by following the
2346+
* update chain and we never switch from ExecMergeNotMatched() to
2347+
* ExecMergeMatched(), there is no risk of a livelock.
2348+
*/
2349+
#if PG17_LT
2350+
matched = tupleid != NULL;
2351+
#else
2352+
matched = tupleid != NULL || oldtuple != NULL;
2353+
#endif
2354+
if (matched)
2355+
rslot = ExecMergeMatched(context, resultRelInfo, tupleid, oldtuple,
2356+
canSetTag, &matched);
2357+
2358+
/*
2359+
* Deal with the NOT MATCHED case (either a NOT MATCHED tuple from the
2360+
* join, or a previously MATCHED tuple for which ExecMergeMatched() set
2361+
* "matched" to false, indicating that it no longer matches).
2362+
*/
2363+
if (!matched)
2364+
{
2365+
/*
2366+
* If a concurrent update turned a MATCHED case into a NOT MATCHED
2367+
* case, and we have both WHEN NOT MATCHED BY SOURCE and WHEN NOT
2368+
* MATCHED [BY TARGET] actions, and there is a RETURNING clause,
2369+
* ExecMergeMatched() may have already executed a WHEN NOT MATCHED BY
2370+
* SOURCE action, and computed the row to return. If so, we cannot
2371+
* execute a WHEN NOT MATCHED [BY TARGET] action now, so mark it as
2372+
* pending (to be processed on the next call to ExecModifyTable()).
2373+
* Otherwise, just process the action now.
2374+
*/
2375+
#if PG17_LT
2376+
ExecMergeNotMatched(context, resultRelInfo, cds, canSetTag);
2377+
#else
2378+
if (rslot == NULL)
2379+
rslot = ExecMergeNotMatched(context, resultRelInfo, cds, canSetTag);
2380+
else
2381+
context->mtstate->mt_merge_pending_not_matched = context->planSlot;
2382+
#endif
2383+
}
2384+
2385+
return rslot;
2386+
}
2387+
22782388

22792389
static void fireASTriggers(ModifyTableState *node);
22802390
static void fireBSTriggers(ModifyTableState *node);
@@ -3431,83 +3541,3 @@ ExecMergeNotMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
34313541
return rslot;
34323542
}
34333543

3434-
/*
3435-
* Perform MERGE.
3436-
*/
3437-
TupleTableSlot *
3438-
ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ChunkDispatchState *cds,
3439-
ItemPointer tupleid, HeapTuple oldtuple, bool canSetTag)
3440-
{
3441-
bool matched;
3442-
TupleTableSlot *rslot = NULL;
3443-
3444-
/*-----
3445-
* If we are dealing with a WHEN MATCHED case (tupleid is valid), we
3446-
* execute the first action for which the additional WHEN MATCHED AND
3447-
* quals pass. If an action without quals is found, that action is
3448-
* executed.
3449-
*
3450-
* Similarly, if we are dealing with WHEN NOT MATCHED case, we look at
3451-
* the given WHEN NOT MATCHED actions in sequence until one passes.
3452-
*
3453-
* Things get interesting in case of concurrent update/delete of the
3454-
* target tuple. Such concurrent update/delete is detected while we are
3455-
* executing a WHEN MATCHED action.
3456-
*
3457-
* A concurrent update can:
3458-
*
3459-
* 1. modify the target tuple so that it no longer satisfies the
3460-
* additional quals attached to the current WHEN MATCHED action
3461-
*
3462-
* In this case, we are still dealing with a WHEN MATCHED case.
3463-
* We recheck the list of WHEN MATCHED actions from the start and
3464-
* choose the first one that satisfies the new target tuple.
3465-
*
3466-
* 2. modify the target tuple so that the join quals no longer pass and
3467-
* hence the source tuple no longer has a match.
3468-
*
3469-
* In this case, the source tuple no longer matches the target tuple,
3470-
* so we now instead find a qualifying WHEN NOT MATCHED action to
3471-
* execute.
3472-
*
3473-
* XXX Hmmm, what if the updated tuple would now match one that was
3474-
* considered NOT MATCHED so far?
3475-
*
3476-
* A concurrent delete changes a WHEN MATCHED case to WHEN NOT MATCHED.
3477-
*
3478-
* ExecMergeMatched takes care of following the update chain and
3479-
* re-finding the qualifying WHEN MATCHED action, as long as the updated
3480-
* target tuple still satisfies the join quals, i.e., it remains a WHEN
3481-
* MATCHED case. If the tuple gets deleted or the join quals fail, it
3482-
* returns and we try ExecMergeNotMatched. Given that ExecMergeMatched
3483-
* always make progress by following the update chain and we never switch
3484-
* from ExecMergeNotMatched to ExecMergeMatched, there is no risk of a
3485-
* livelock.
3486-
*/
3487-
#if PG17_GE
3488-
matched = tupleid != NULL || oldtuple != NULL;
3489-
#else
3490-
matched = tupleid != NULL;
3491-
#endif
3492-
if (matched)
3493-
rslot = ExecMergeMatched(context, resultRelInfo, tupleid, oldtuple, canSetTag, &matched);
3494-
3495-
/*
3496-
* Either we were dealing with a NOT MATCHED tuple or
3497-
* ExecMergeMatched() returned "false", indicating the previously
3498-
* MATCHED tuple no longer matches.
3499-
*/
3500-
if (!matched)
3501-
{
3502-
#if PG17_GE
3503-
if (rslot == NULL)
3504-
rslot = ExecMergeNotMatched(context, resultRelInfo, cds, canSetTag);
3505-
else
3506-
context->mtstate->mt_merge_pending_not_matched = context->planSlot;
3507-
#else
3508-
(void) ExecMergeNotMatched(context, resultRelInfo, cds, canSetTag);
3509-
#endif
3510-
}
3511-
3512-
return rslot;
3513-
}

0 commit comments

Comments
 (0)