1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-27 23:21:58 +03:00

Fix MakeTransitionCaptureState() to return a consistent result

When an UPDATE trigger referencing a new table and a DELETE trigger
referencing an old table are both present, MakeTransitionCaptureState()
returns an inconsistent result for UPDATE commands in its set of flags
and tuplestores holding the TransitionCaptureState for transition
tables.

As proved by the test added here, this issue causes a crash in v14 and
earlier versions (down to 11, actually, older versions do not support
triggers on partitioned tables) during cross-partition updates on a
partitioned table.  v15 and newer versions are safe thanks to
7103ebb7aa.

This commit fixes the function so that it returns a consistent state
by using portions of the changes made in commit 7103ebb7aa for v13 and
v14.  v15 and newer versions are slightly tweaked to match with the
older versions, mainly for consistency across branches.

Author: Kyotaro Horiguchi
Discussion: https://postgr.es/m/20250207.150238.968446820828052276.horikyota.ntt@gmail.com
Backpatch-through: 13
This commit is contained in:
Michael Paquier
2025-02-13 16:31:12 +09:00
parent 412047f19f
commit 5209058240
3 changed files with 115 additions and 16 deletions

View File

@ -4413,8 +4413,10 @@ TransitionCaptureState *
MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
{
TransitionCaptureState *state;
bool need_old,
need_new;
bool need_old_upd,
need_new_upd,
need_old_del,
need_new_ins;
AfterTriggersTableData *table;
MemoryContext oldcxt;
ResourceOwner saveResourceOwner;
@ -4426,23 +4428,25 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
switch (cmdType)
{
case CMD_INSERT:
need_old = false;
need_new = trigdesc->trig_insert_new_table;
need_old_upd = need_old_del = need_new_upd = false;
need_new_ins = trigdesc->trig_insert_new_table;
break;
case CMD_UPDATE:
need_old = trigdesc->trig_update_old_table;
need_new = trigdesc->trig_update_new_table;
need_old_upd = trigdesc->trig_update_old_table;
need_new_upd = trigdesc->trig_update_new_table;
need_old_del = need_new_ins = false;
break;
case CMD_DELETE:
need_old = trigdesc->trig_delete_old_table;
need_new = false;
need_old_del = trigdesc->trig_delete_old_table;
need_old_upd = need_new_upd = need_new_ins = false;
break;
default:
elog(ERROR, "unexpected CmdType: %d", (int) cmdType);
need_old = need_new = false; /* keep compiler quiet */
/* keep compiler quiet */
need_old_upd = need_new_upd = need_old_del = need_new_ins = false;
break;
}
if (!need_old && !need_new)
if (!need_old_upd && !need_new_upd && !need_new_ins && !need_old_del)
return NULL;
/* Check state, like AfterTriggerSaveEvent. */
@ -4472,9 +4476,9 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
saveResourceOwner = CurrentResourceOwner;
CurrentResourceOwner = CurTransactionResourceOwner;
if (need_old && table->old_tuplestore == NULL)
if ((need_old_upd || need_old_del) && table->old_tuplestore == NULL)
table->old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
if (need_new && table->new_tuplestore == NULL)
if ((need_new_upd || need_new_ins) && table->new_tuplestore == NULL)
table->new_tuplestore = tuplestore_begin_heap(false, false, work_mem);
CurrentResourceOwner = saveResourceOwner;
@ -4482,10 +4486,10 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
/* Now build the TransitionCaptureState struct, in caller's context */
state = (TransitionCaptureState *) palloc0(sizeof(TransitionCaptureState));
state->tcs_delete_old_table = trigdesc->trig_delete_old_table;
state->tcs_update_old_table = trigdesc->trig_update_old_table;
state->tcs_update_new_table = trigdesc->trig_update_new_table;
state->tcs_insert_new_table = trigdesc->trig_insert_new_table;
state->tcs_delete_old_table = need_old_del;
state->tcs_update_old_table = need_old_upd;
state->tcs_update_new_table = need_new_upd;
state->tcs_insert_new_table = need_new_ins;
state->tcs_private = table;
return state;