1
0
mirror of https://github.com/postgres/postgres.git synced 2025-05-05 09:19:17 +03:00

Fix assertions with RI triggers in heap_update and heap_delete.

If the tuple being updated is not visible to the crosscheck snapshot,
we return TM_Updated but the assertions would not hold in that case.
Move them to before the cross-check.

Fixes bug #17893. Backpatch to all supported versions.

Author: Alexander Lakhin
Backpatch-through: 12
Discussion: https://www.postgresql.org/message-id/17893-35847009eec517b5%40postgresql.org
This commit is contained in:
Heikki Linnakangas 2023-11-28 11:59:09 +02:00
parent fef92f9ba1
commit 2873fbfe0d
4 changed files with 64 additions and 20 deletions

View File

@ -2856,13 +2856,7 @@ l1:
result = TM_Deleted; result = TM_Deleted;
} }
if (crosscheck != InvalidSnapshot && result == TM_Ok) /* sanity check the result HeapTupleSatisfiesUpdate() and the logic above */
{
/* Perform additional check for transaction-snapshot mode RI updates */
if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer))
result = TM_Updated;
}
if (result != TM_Ok) if (result != TM_Ok)
{ {
Assert(result == TM_SelfModified || Assert(result == TM_SelfModified ||
@ -2872,6 +2866,17 @@ l1:
Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID)); Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID));
Assert(result != TM_Updated || Assert(result != TM_Updated ||
!ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid)); !ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid));
}
if (crosscheck != InvalidSnapshot && result == TM_Ok)
{
/* Perform additional check for transaction-snapshot mode RI updates */
if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer))
result = TM_Updated;
}
if (result != TM_Ok)
{
tmfd->ctid = tp.t_data->t_ctid; tmfd->ctid = tp.t_data->t_ctid;
tmfd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data); tmfd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
if (result == TM_SelfModified) if (result == TM_SelfModified)
@ -3483,16 +3488,7 @@ l2:
result = TM_Deleted; result = TM_Deleted;
} }
if (crosscheck != InvalidSnapshot && result == TM_Ok) /* Sanity check the result HeapTupleSatisfiesUpdate() and the logic above */
{
/* Perform additional check for transaction-snapshot mode RI updates */
if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
{
result = TM_Updated;
Assert(!ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid));
}
}
if (result != TM_Ok) if (result != TM_Ok)
{ {
Assert(result == TM_SelfModified || Assert(result == TM_SelfModified ||
@ -3502,6 +3498,17 @@ l2:
Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)); Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
Assert(result != TM_Updated || Assert(result != TM_Updated ||
!ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid)); !ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid));
}
if (crosscheck != InvalidSnapshot && result == TM_Ok)
{
/* Perform additional check for transaction-snapshot mode RI updates */
if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
result = TM_Updated;
}
if (result != TM_Ok)
{
tmfd->ctid = oldtup.t_data->t_ctid; tmfd->ctid = oldtup.t_data->t_ctid;
tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data); tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
if (result == TM_SelfModified) if (result == TM_SelfModified)

View File

@ -1455,8 +1455,8 @@ table_multi_insert(Relation rel, TupleTableSlot **slots, int nslots,
* TM_BeingModified (the last only possible if wait == false). * TM_BeingModified (the last only possible if wait == false).
* *
* In the failure cases, the routine fills *tmfd with the tuple's t_ctid, * In the failure cases, the routine fills *tmfd with the tuple's t_ctid,
* t_xmax, and, if possible, and, if possible, t_cmax. See comments for * t_xmax, and, if possible, t_cmax. See comments for struct
* struct TM_FailureData for additional info. * TM_FailureData for additional info.
*/ */
static inline TM_Result static inline TM_Result
table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid, table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,

View File

@ -122,3 +122,25 @@ a
1 1
(1 row) (1 row)
starting permutation: s2ip2 s1brr s1ifp2 s2brr s2dp2 s1c s2c
step s2ip2: INSERT INTO pk_noparted VALUES (2);
step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s1ifp2: INSERT INTO fk_parted_pk VALUES (2);
step s2brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s2dp2: DELETE FROM pk_noparted WHERE a = 2; <waiting ...>
step s1c: COMMIT;
step s2dp2: <... completed>
ERROR: could not serialize access due to concurrent update
step s2c: COMMIT;
starting permutation: s2ip2 s1brr s1ifn2 s2brr s2dp2 s1c s2c
step s2ip2: INSERT INTO pk_noparted VALUES (2);
step s1brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s1ifn2: INSERT INTO fk_noparted_sn VALUES (2);
step s2brr: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s2dp2: DELETE FROM pk_noparted WHERE a = 2; <waiting ...>
step s1c: COMMIT;
step s2dp2: <... completed>
ERROR: could not serialize access due to concurrent update
step s2c: COMMIT;

View File

@ -13,6 +13,11 @@ setup
CREATE TABLE fk_noparted ( CREATE TABLE fk_noparted (
a int REFERENCES fk_parted_pk ON DELETE NO ACTION INITIALLY DEFERRED a int REFERENCES fk_parted_pk ON DELETE NO ACTION INITIALLY DEFERRED
); );
CREATE TABLE fk_noparted_sn (
a int REFERENCES pk_noparted ON DELETE SET NULL
);
INSERT INTO pk_noparted VALUES (1); INSERT INTO pk_noparted VALUES (1);
INSERT INTO fk_parted_pk VALUES (1); INSERT INTO fk_parted_pk VALUES (1);
INSERT INTO fk_noparted VALUES (1); INSERT INTO fk_noparted VALUES (1);
@ -20,7 +25,7 @@ setup
teardown teardown
{ {
DROP TABLE pk_noparted, fk_parted_pk, fk_noparted; DROP TABLE pk_noparted, fk_parted_pk, fk_noparted, fk_noparted_sn;
} }
session s1 session s1
@ -28,6 +33,7 @@ step s1brr { BEGIN ISOLATION LEVEL REPEATABLE READ; }
step s1brc { BEGIN ISOLATION LEVEL READ COMMITTED; } step s1brc { BEGIN ISOLATION LEVEL READ COMMITTED; }
step s1ifp2 { INSERT INTO fk_parted_pk VALUES (2); } step s1ifp2 { INSERT INTO fk_parted_pk VALUES (2); }
step s1ifp1 { INSERT INTO fk_parted_pk VALUES (1); } step s1ifp1 { INSERT INTO fk_parted_pk VALUES (1); }
step s1ifn2 { INSERT INTO fk_noparted_sn VALUES (2); }
step s1dfp { DELETE FROM fk_parted_pk WHERE a = 1; } step s1dfp { DELETE FROM fk_parted_pk WHERE a = 1; }
step s1c { COMMIT; } step s1c { COMMIT; }
step s1sfp { SELECT * FROM fk_parted_pk; } step s1sfp { SELECT * FROM fk_parted_pk; }
@ -38,6 +44,7 @@ session s2
step s2brr { BEGIN ISOLATION LEVEL REPEATABLE READ; } step s2brr { BEGIN ISOLATION LEVEL REPEATABLE READ; }
step s2brc { BEGIN ISOLATION LEVEL READ COMMITTED; } step s2brc { BEGIN ISOLATION LEVEL READ COMMITTED; }
step s2ip2 { INSERT INTO pk_noparted VALUES (2); } step s2ip2 { INSERT INTO pk_noparted VALUES (2); }
step s2dp2 { DELETE FROM pk_noparted WHERE a = 2; }
step s2ifn2 { INSERT INTO fk_noparted VALUES (2); } step s2ifn2 { INSERT INTO fk_noparted VALUES (2); }
step s2c { COMMIT; } step s2c { COMMIT; }
step s2sfp { SELECT * FROM fk_parted_pk; } step s2sfp { SELECT * FROM fk_parted_pk; }
@ -59,3 +66,11 @@ permutation s1brc s2brc s2ip2 s1sp s2c s1sp s1ifp2 s2brc s2sfp s1c s1sfp s2ifn2
# the same no matter the snapshot mode # the same no matter the snapshot mode
permutation s1brr s1dfp s1ifp1 s1c s1sfn permutation s1brr s1dfp s1ifp1 s1c s1sfn
permutation s1brc s1dfp s1ifp1 s1c s1sfn permutation s1brc s1dfp s1ifp1 s1c s1sfn
# trying to delete a row through DELETE CASCADE, whilst that row is deleted
# in a concurrent transaction
permutation s2ip2 s1brr s1ifp2 s2brr s2dp2 s1c s2c
# trying to update a row through DELETE SET NULL, whilst that row is deleted
# in a concurrent transaction
permutation s2ip2 s1brr s1ifn2 s2brr s2dp2 s1c s2c