pgsql: Fix concurrent update issues with MERGE. - Mailing list pgsql-committers

From Dean Rasheed
Subject pgsql: Fix concurrent update issues with MERGE.
Date
Msg-id E1pbfMi-003JQ1-3N@gemulon.postgresql.org
Whole thread Raw
List pgsql-committers
Fix concurrent update issues with MERGE.

If MERGE attempts an UPDATE or DELETE on a table with BEFORE ROW
triggers, or a cross-partition UPDATE (with or without triggers), and
a concurrent UPDATE or DELETE happens, the merge code would fail.

In some cases this would lead to a crash, while in others it would
cause the wrong merge action to be executed, or no action at all. The
immediate cause of the crash was the trigger code calling
ExecGetUpdateNewTuple() as part of the EPQ mechanism, which fails
because during a merge ri_projectNew is NULL, since merge has its own
per-action projection information, which ExecGetUpdateNewTuple() knows
nothing about.

Fix by arranging for the trigger code to exit early, returning the
TM_Result and TM_FailureData information, if a concurrent modification
is detected, allowing the merge code to do the necessary EPQ handling
in its own way. Similarly, prevent the cross-partition update code
from doing any EPQ processing for a merge, allowing the merge code to
work out what it needs to do.

This leads to a number of simplifications in nodeModifyTable.c. Most
notably, the ModifyTableContext->GetUpdateNewTuple() callback is no
longer needed, and mergeGetUpdateNewTuple() can be deleted, since
there is no longer any requirement for get-update-new-tuple during a
merge. Similarly, ModifyTableContext->cpUpdateRetrySlot is no longer
needed. Thus ExecGetUpdateNewTuple() and the retry_slot handling of
ExecCrossPartitionUpdate() can be restored to how they were in v14,
before the merge code was added, and ExecMergeMatched() no longer
needs any special-case handling for cross-partition updates.

While at it, tidy up ExecUpdateEpilogue() a bit, making it handle
recheckIndexes locally, rather than passing it in as a parameter,
ensuring that it is freed properly. This dates back to when it was
split off from ExecUpdate() to support merge.

Per bug #17809 from Alexander Lakhin, and follow-up investigation of
bug #17792, also from Alexander Lakhin.

Back-patch to v15, where MERGE was introduced, taking care to preserve
backwards-compatibility of the trigger API in v15 for any extensions
that might use it.

Discussion:
  https://postgr.es/m/17809-9e6650bef133f0fe%40postgresql.org
  https://postgr.es/m/17792-0f89452029662c36%40postgresql.org

Branch
------
master

Details
-------
https://git.postgresql.org/pg/commitdiff/9321c79c86e6a6a4eac22e2235a21a8b68388723

Modified Files
--------------
src/backend/commands/trigger.c                     |  27 ++-
src/backend/executor/execReplication.c             |   4 +-
src/backend/executor/nodeModifyTable.c             | 173 +++++----------
src/include/commands/trigger.h                     |   5 +-
src/test/isolation/expected/merge-delete.out       | 195 +++++++++++++----
.../isolation/expected/merge-match-recheck.out     | 233 +++++++++++++++++++++
src/test/isolation/specs/merge-delete.spec         |  66 +++++-
src/test/isolation/specs/merge-match-recheck.spec  | 107 ++++++++++
8 files changed, 636 insertions(+), 174 deletions(-)


pgsql-committers by date:

Previous
From: Peter Eisentraut
Date:
Subject: pgsql: Fix expected test output
Next
From: Dean Rasheed
Date:
Subject: pgsql: Fix MERGE command tag for actions blocked by BEFORE ROW triggers