Re: wrong results: merge when not matched by source

From: Dean Rasheed <dean(dot)a(dot)rasheed(at)gmail(dot)com>
To: Tender Wang <tndrwang(at)gmail(dot)com>
Cc: Jeff Davis <pgsql(at)j-davis(dot)com>, pgsql-bugs(at)postgresql(dot)org
Subject: Re: wrong results: merge when not matched by source
Date: 2026-09-18 11:03:25
Message-ID: CAEZATCU795F4OP+NvDvEUjcmND6pk0iOdePNGQNQvQkhTvhJfA@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

On Fri, 18 Sept 2026 at 03:02, Tender Wang <tndrwang(at)gmail(dot)com> wrote:
>
> Jeff Davis <pgsql(at)j-davis(dot)com> 于2026年9月18日周五 03:18写道:
> >
> > -- Expected: only UPDATE of key=1; table is {(1, src)}.
> > -- Actual: also INSERT of (NULL, NULL).
>
>
> I think the problem is that the TM_Deleted case in
> ExecMergeMatched() unconditionally sets *matched to false.
>
> This is correct when the candidate row was originally a MATCHED row:
> the source row still exists after the target row is concurrently
> deleted, so it should be processed by the NOT MATCHED BY TARGET
> actions.
>
> However, it is not correct when processing an original NOT MATCHED BY
> SOURCE row. In that case, the source row was already absent, and after
> the concurrent deletion of the target row, there is no candidate row
> left to process. Setting *matched to false causes ExecMerge() to
> call ExecMergeNotMatched(), which executes the NOT MATCHED BY TARGET
> INSERT using the null source side of the original join row.

Yes, that analysis is correct. If there was no source row, and the
target row is concurrently deleted, then there's nothing left to do.
It shouldn't execute a NOT MATCHED BY TARGET action if there was never
a source row to begin with.

> A tentative fix is to set *matched to false only if the current
> action is a MATCHED action, as in the attached patch.

That fix looks good to me. Nice and simple.

> There is another TM_Deleted case below, reached while handling
> TM_Updated and following the update chain:
> ...
> case TM_Deleted:
> *matched = false;
> goto out;
> ...
>
> I think that case may need similar treatment. The existing
> was_matched variable could perhaps be used there:
> ...
> if (was_matched)
> *matched = false;

Yes, that's basically the same. It can be triggered by a slight
modification to the original test case, making Session 1 do an UPDATE
before the DELETE:

-- Session 1:
BEGIN ISOLATION LEVEL READ COMMITTED;
UPDATE target SET val = 'nms-updated' WHERE key = 2; -- holds the NMS row
DELETE FROM target WHERE key = 2;

> The attached patch is intended to show the proposed fix and does not
> yet include a regression test.

Attached is an update with the fix for the second case, and tests for both.

Regards,
Dean

Attachment Content-Type Size
v2-fix-concurrent-delete-issue-with-merge-when-not-matched-by-source.patch text/x-patch 11.3 KB

In response to

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message Dean Rasheed 2026-09-18 12:21:53 Re: wrong results: merge when not matched by source
Previous Message Andrey Rachitskiy 2026-09-18 09:37:50 Re: BUG #19697: HAVING-to-WHERE transfer gives wrong count when scale(numeric) distinguishes equal grouping values