diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 9498cbb..3451891 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -2198,13 +2198,9 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid, tup->t_data->t_infomask &= ~(HEAP_XACT_MASK); tup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK); tup->t_data->t_infomask |= HEAP_XMAX_INVALID; + HeapTupleHeaderSetXmin(tup->t_data, xid); if (options & HEAP_INSERT_FROZEN) - { - tup->t_data->t_infomask |= HEAP_XMIN_COMMITTED; - HeapTupleHeaderSetXmin(tup->t_data, FrozenTransactionId); - } - else - HeapTupleHeaderSetXmin(tup->t_data, xid); + HeapTupleHeaderSetXminFrozen(tup->t_data); HeapTupleHeaderSetCmin(tup->t_data, cid); HeapTupleHeaderSetXmax(tup->t_data, 0); /* for cleanliness */ tup->t_tableOid = RelationGetRelid(relation); @@ -5087,19 +5083,15 @@ heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid, bool changed = false; TransactionId xid; - xid = HeapTupleHeaderGetXmin(tuple); - if (TransactionIdIsNormal(xid) && - TransactionIdPrecedes(xid, cutoff_xid)) + if (!HeapTupleHeaderXminFrozen(tuple)) { - HeapTupleHeaderSetXmin(tuple, FrozenTransactionId); - - /* - * Might as well fix the hint bits too; usually XMIN_COMMITTED will - * already be set here, but there's a small chance not. - */ - Assert(!(tuple->t_infomask & HEAP_XMIN_INVALID)); - tuple->t_infomask |= HEAP_XMIN_COMMITTED; - changed = true; + xid = HeapTupleHeaderGetXmin(tuple); + if (TransactionIdIsNormal(xid) && + TransactionIdPrecedes(xid, cutoff_xid)) + { + HeapTupleHeaderSetXminFrozen(tuple); + changed = true; + } } /* @@ -5152,8 +5144,7 @@ heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid, * Might as well fix the hint bits too; usually XMIN_COMMITTED * will already be set here, but there's a small chance not. */ - Assert(!(tuple->t_infomask & HEAP_XMIN_INVALID)); - tuple->t_infomask |= HEAP_XMIN_COMMITTED; + HeapTupleHeaderSetXminCommitted(tuple); changed = true; } } @@ -5443,10 +5434,13 @@ heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid, { TransactionId xid; - xid = HeapTupleHeaderGetXmin(tuple); - if (TransactionIdIsNormal(xid) && - TransactionIdPrecedes(xid, cutoff_xid)) - return true; + if (!HeapTupleHeaderXminFrozen(tuple)) + { + xid = HeapTupleHeaderGetXmin(tuple); + if (TransactionIdIsNormal(xid) && + TransactionIdPrecedes(xid, cutoff_xid)) + return true; + } if (!(tuple->t_infomask & HEAP_XMAX_INVALID)) { @@ -5572,12 +5566,10 @@ HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple, * This needs to work on both master and standby, where it is used to * assess btree delete records. */ - if ((tuple->t_infomask & HEAP_XMIN_COMMITTED) || - (!(tuple->t_infomask & HEAP_XMIN_COMMITTED) && - !(tuple->t_infomask & HEAP_XMIN_INVALID) && - TransactionIdDidCommit(xmin))) + if (HeapTupleHeaderXminCommitted(tuple) || + (!HeapTupleHeaderXminInvalid(tuple) && TransactionIdDidCommit(xmin))) { - if (xmax != xmin && + if ((HeapTupleHeaderXminFrozen(tuple) || xmax != xmin) && TransactionIdFollows(xmax, *latestRemovedXid)) *latestRemovedXid = xmax; } diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index a3aad3a..09650d8 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -437,6 +437,7 @@ rewrite_heap_tuple(RewriteState state, * RECENTLY_DEAD if and only if the xmin is not before OldestXmin. */ if ((new_tuple->t_data->t_infomask & HEAP_UPDATED) && + !HeapTupleHeaderXminFrozen(new_tuple->t_data) && !TransactionIdPrecedes(HeapTupleHeaderGetXmin(new_tuple->t_data), state->rs_oldest_xmin)) { diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 7966558..280c13c 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -2317,6 +2317,7 @@ IndexBuildHeapScan(Relation heapRelation, * before commit there. Give a warning if neither case * applies. */ + Assert(!HeapTupleHeaderXminFrozen(heapTuple->t_data)); xwait = HeapTupleHeaderGetXmin(heapTuple->t_data); if (!TransactionIdIsCurrentTransactionId(xwait)) { diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index d6d20fd..e2fc9a2 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -1177,6 +1177,7 @@ acquire_sample_rows(Relation onerel, int elevel, * has to adjust the numbers we send to the stats * collector to make this come out right.) */ + Assert(!HeapTupleHeaderXminFrozen(targtuple.t_data)); if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(targtuple.t_data))) { sample_it = true; diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 878b625..01009f9 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -981,6 +981,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, * there. Give a warning if neither case applies; but in any * case we had better copy it. */ + Assert(!HeapTupleHeaderXminFrozen(tuple->t_data)); if (!is_system_catalog && !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data))) elog(WARNING, "concurrent insert in progress within table \"%s\"", diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 49e409a..522ae33 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -364,10 +364,10 @@ fill_seq_with_data(Relation rel, HeapTuple tuple) item = PageGetItem((Page) page, itemId); HeapTupleHeaderSetXmin((HeapTupleHeader) item, FrozenTransactionId); - ((HeapTupleHeader) item)->t_infomask |= HEAP_XMIN_COMMITTED; + HeapTupleHeaderSetXminFrozen((HeapTupleHeader) item); HeapTupleHeaderSetXmin(tuple->t_data, FrozenTransactionId); - tuple->t_data->t_infomask |= HEAP_XMIN_COMMITTED; + HeapTupleHeaderSetXminFrozen(tuple->t_data); } MarkBufferDirty(buf); diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 9efe244..ebbea7c 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -1201,7 +1201,8 @@ AlterEnum(AlterEnumStmt *stmt, bool isTopLevel) * some cases that could theoretically be safe; but fortunately pg_dump * only needs the simplest case. */ - if (HeapTupleHeaderGetXmin(tup->t_data) == GetCurrentTransactionId() && + if (!HeapTupleHeaderXminFrozen(tup->t_data) && + HeapTupleHeaderGetXmin(tup->t_data) == GetCurrentTransactionId() && !(tup->t_data->t_infomask & HEAP_UPDATED)) /* safe to do inside transaction block */ ; else diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c index 9d30415..af2639c 100644 --- a/src/backend/commands/vacuumlazy.c +++ b/src/backend/commands/vacuumlazy.c @@ -787,16 +787,19 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, { TransactionId xmin; - if (!(tuple.t_data->t_infomask & HEAP_XMIN_COMMITTED)) + if (!HeapTupleHeaderXminCommitted(tuple.t_data)) { all_visible = false; break; } + /* * The inserter definitely committed. But is it old * enough that everyone sees it as committed? */ + if (HeapTupleHeaderXminFrozen(tuple.t_data)) + break; xmin = HeapTupleHeaderGetXmin(tuple.t_data); if (!TransactionIdPrecedes(xmin, OldestXmin)) { @@ -1709,7 +1712,7 @@ heap_page_is_all_visible(Buffer buf, TransactionId *visibility_cutoff_xid) TransactionId xmin; /* Check comments in lazy_scan_heap. */ - if (!(tuple.t_data->t_infomask & HEAP_XMIN_COMMITTED)) + if (!HeapTupleHeaderXminCommitted(tuple.t_data)) { all_visible = false; break; @@ -1719,6 +1722,8 @@ heap_page_is_all_visible(Buffer buf, TransactionId *visibility_cutoff_xid) * The inserter definitely committed. But is it old * enough that everyone sees it as committed? */ + if (HeapTupleHeaderXminFrozen(tuple.t_data)) + break; xmin = HeapTupleHeaderGetXmin(tuple.t_data); if (!TransactionIdPrecedes(xmin, OldestXmin)) { diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 954666c..e22be0e 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -188,6 +188,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * src/backend/access/heap/README.HOT for discussion. */ if (index->indcheckxmin && + !HeapTupleHeaderXminFrozen(indexRelation->rd_indextuple->t_data) && !TransactionIdPrecedes(HeapTupleHeaderGetXmin(indexRelation->rd_indextuple->t_data), TransactionXmin)) { diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c index 6029cfb..a4c6924 100644 --- a/src/backend/storage/lmgr/predicate.c +++ b/src/backend/storage/lmgr/predicate.c @@ -2475,7 +2475,10 @@ PredicateLockTuple(Relation relation, HeapTuple tuple, Snapshot snapshot) { TransactionId myxid; - targetxmin = HeapTupleHeaderGetXmin(tuple->t_data); + if (HeapTupleHeaderXminFrozen(tuple->t_data)) + targetxmin = FrozenTransactionId; + else + targetxmin = HeapTupleHeaderGetXmin(tuple->t_data); myxid = GetTopTransactionIdIfAny(); if (TransactionIdIsValid(myxid)) @@ -3901,6 +3904,7 @@ CheckForSerializableConflictOut(bool visible, Relation relation, case HEAPTUPLE_LIVE: if (visible) return; + Assert(!HeapTupleHeaderXminFrozen(tuple->t_data)); xid = HeapTupleHeaderGetXmin(tuple->t_data); break; case HEAPTUPLE_RECENTLY_DEAD: @@ -3912,6 +3916,7 @@ CheckForSerializableConflictOut(bool visible, Relation relation, xid = HeapTupleHeaderGetUpdateXid(tuple->t_data); break; case HEAPTUPLE_INSERT_IN_PROGRESS: + Assert(!HeapTupleHeaderXminFrozen(tuple->t_data)); xid = HeapTupleHeaderGetXmin(tuple->t_data); break; case HEAPTUPLE_DEAD: @@ -4279,12 +4284,19 @@ CheckForSerializableConflictIn(Relation relation, HeapTuple tuple, */ if (tuple != NULL) { + TransactionId xmin; + + if (HeapTupleHeaderXminFrozen(tuple->t_data)) + xmin = FrozenTransactionId; + else + xmin = HeapTupleHeaderGetXmin(tuple->t_data); + SET_PREDICATELOCKTARGETTAG_TUPLE(targettag, relation->rd_node.dbNode, relation->rd_id, ItemPointerGetBlockNumber(&(tuple->t_data->t_ctid)), ItemPointerGetOffsetNumber(&(tuple->t_data->t_ctid)), - HeapTupleHeaderGetXmin(tuple->t_data)); + xmin); CheckTargetForConflictsIn(&targettag); } diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 4322844..a67643a 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -2165,7 +2165,8 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, * UPDATE check. (We could skip this if we knew the INSERT * trigger already fired, but there is no easy way to know that.) */ - if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(old_row->t_data))) + if (!HeapTupleHeaderXminFrozen(old_row->t_data) && + TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(old_row->t_data))) return true; /* If all old and new key values are equal, no check is needed */ @@ -2202,7 +2203,8 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, * UPDATE check. (We could skip this if we knew the INSERT * trigger already fired, but there is no easy way to know that.) */ - if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(old_row->t_data))) + if (!HeapTupleHeaderXminFrozen(old_row->t_data) && + TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(old_row->t_data))) return true; /* If all old and new key values are equal, no check is needed */ diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 7888d38..239582e 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -1764,6 +1764,7 @@ RelationReloadIndexInfo(Relation relation) { HeapTuple tuple; Form_pg_index index; + TransactionId xmin; tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(RelationGetRelid(relation))); @@ -1789,8 +1790,11 @@ RelationReloadIndexInfo(Relation relation) relation->rd_index->indislive = index->indislive; /* Copy xmin too, as that is needed to make sense of indcheckxmin */ - HeapTupleHeaderSetXmin(relation->rd_indextuple->t_data, - HeapTupleHeaderGetXmin(tuple->t_data)); + if (HeapTupleHeaderXminFrozen(relation->rd_indextuple->t_data)) + xmin = FrozenTransactionId; + else + xmin = HeapTupleHeaderGetXmin(relation->rd_indextuple->t_data); + HeapTupleHeaderSetXmin(relation->rd_indextuple->t_data, xmin); ReleaseSysCache(tuple); } diff --git a/src/backend/utils/time/combocid.c b/src/backend/utils/time/combocid.c index 923355d..baacd4a 100644 --- a/src/backend/utils/time/combocid.c +++ b/src/backend/utils/time/combocid.c @@ -148,10 +148,12 @@ HeapTupleHeaderAdjustCmax(HeapTupleHeader tup, /* * If we're marking a tuple deleted that was inserted by (any * subtransaction of) our transaction, we need to use a combo command id. - * Test for HEAP_XMIN_COMMITTED first, because it's cheaper than a - * TransactionIdIsCurrentTransactionId call. + * It's cheaper to test HeapTupleHeaderXminCommitted before checking + * TransactionIdIsCurrentTransactionId, but it's also necessary for + * correctness: if HeapTupleHeaderXminCommitted returns true, the tuple + * might even be frozen. */ - if (!(tup->t_infomask & HEAP_XMIN_COMMITTED) && + if (!HeapTupleHeaderXminCommitted(tup) && TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup))) { CommandId cmin = HeapTupleHeaderGetCmin(tup); diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c index 24384b4..03a0e57 100644 --- a/src/backend/utils/time/tqual.c +++ b/src/backend/utils/time/tqual.c @@ -165,9 +165,9 @@ HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer, bool HeapTupleSatisfiesSelf(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer) { - if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) + if (!HeapTupleHeaderXminCommitted(tuple)) { - if (tuple->t_infomask & HEAP_XMIN_INVALID) + if (HeapTupleHeaderXminInvalid(tuple)) return false; /* Used by pre-9.0 binary upgrades */ @@ -353,9 +353,9 @@ HeapTupleSatisfiesSelf(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer) bool HeapTupleSatisfiesNow(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer) { - if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) + if (!HeapTupleHeaderXminCommitted(tuple)) { - if (tuple->t_infomask & HEAP_XMIN_INVALID) + if (HeapTupleHeaderXminInvalid(tuple)) return false; /* Used by pre-9.0 binary upgrades */ @@ -549,9 +549,9 @@ bool HeapTupleSatisfiesToast(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer) { - if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) + if (!HeapTupleHeaderXminCommitted(tuple)) { - if (tuple->t_infomask & HEAP_XMIN_INVALID) + if (HeapTupleHeaderXminInvalid(tuple)) return false; /* Used by pre-9.0 binary upgrades */ @@ -630,9 +630,9 @@ HTSU_Result HeapTupleSatisfiesUpdate(HeapTupleHeader tuple, CommandId curcid, Buffer buffer) { - if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) + if (!HeapTupleHeaderXminCommitted(tuple)) { - if (tuple->t_infomask & HEAP_XMIN_INVALID) + if (HeapTupleHeaderXminInvalid(tuple)) return HeapTupleInvisible; /* Used by pre-9.0 binary upgrades */ @@ -853,9 +853,9 @@ HeapTupleSatisfiesDirty(HeapTupleHeader tuple, Snapshot snapshot, { snapshot->xmin = snapshot->xmax = InvalidTransactionId; - if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) + if (!HeapTupleHeaderXminCommitted(tuple)) { - if (tuple->t_infomask & HEAP_XMIN_INVALID) + if (HeapTupleHeaderXminInvalid(tuple)) return false; /* Used by pre-9.0 binary upgrades */ @@ -1042,9 +1042,9 @@ bool HeapTupleSatisfiesMVCC(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer) { - if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) + if (!HeapTupleHeaderXminCommitted(tuple)) { - if (tuple->t_infomask & HEAP_XMIN_INVALID) + if (HeapTupleHeaderXminInvalid(tuple)) return false; /* Used by pre-9.0 binary upgrades */ @@ -1145,7 +1145,8 @@ HeapTupleSatisfiesMVCC(HeapTupleHeader tuple, Snapshot snapshot, * By here, the inserting transaction has committed - have to check * when... */ - if (XidInMVCCSnapshot(HeapTupleHeaderGetXmin(tuple), snapshot)) + if (!HeapTupleHeaderXminFrozen(tuple) + && XidInMVCCSnapshot(HeapTupleHeaderGetXmin(tuple), snapshot)) return false; /* treat as still in progress */ if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */ @@ -1241,9 +1242,9 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin, * If the inserting transaction aborted, then the tuple was never visible * to any other transaction, so we can delete it immediately. */ - if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) + if (!HeapTupleHeaderXminCommitted(tuple)) { - if (tuple->t_infomask & HEAP_XMIN_INVALID) + if (HeapTupleHeaderXminInvalid(tuple)) return HEAPTUPLE_DEAD; /* Used by pre-9.0 binary upgrades */ else if (tuple->t_infomask & HEAP_MOVED_OFF) @@ -1471,8 +1472,8 @@ HeapTupleIsSurelyDead(HeapTupleHeader tuple, TransactionId OldestXmin) * invalid, then we assume it's still alive (since the presumption is that * all relevant hint bits were just set moments ago). */ - if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) - return (tuple->t_infomask & HEAP_XMIN_INVALID) != 0 ? true : false; + if (!HeapTupleHeaderXminCommitted(tuple)) + return HeapTupleHeaderXminInvalid(tuple) ? true : false; /* * If the inserting transaction committed, but any deleting transaction diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h index cd01ecd..56579ef 100644 --- a/src/include/access/htup_details.h +++ b/src/include/access/htup_details.h @@ -174,6 +174,7 @@ struct HeapTupleHeaderData HEAP_XMAX_KEYSHR_LOCK) #define HEAP_XMIN_COMMITTED 0x0100 /* t_xmin committed */ #define HEAP_XMIN_INVALID 0x0200 /* t_xmin invalid/aborted */ +#define HEAP_XMIN_FROZEN (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID) #define HEAP_XMAX_COMMITTED 0x0400 /* t_xmax committed */ #define HEAP_XMAX_INVALID 0x0800 /* t_xmax invalid/aborted */ #define HEAP_XMAX_IS_MULTI 0x1000 /* t_xmax is a MultiXactId */ @@ -253,6 +254,37 @@ struct HeapTupleHeaderData (tup)->t_choice.t_heap.t_xmin = (xid) \ ) +#define HeapTupleHeaderXminCommitted(tup) \ +( \ + ((tup)->t_infomask & HEAP_XMIN_COMMITTED) == HEAP_XMIN_COMMITTED \ +) +#define HeapTupleHeaderXminInvalid(tup) \ +( \ + ((tup)->t_infomask & (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)) == \ + HEAP_XMIN_INVALID \ +) +#define HeapTupleHeaderXminFrozen(tup) \ +( \ + ((tup)->t_infomask & (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)) == \ + HEAP_XMIN_FROZEN \ +) + +#define HeapTupleHeaderSetXminCommitted(tup) \ +( \ + AssertMacro(!HeapTupleHeaderXminInvalid(tup)), \ + ((tup)->t_infomask |= HEAP_XMIN_COMMITTED) \ +) +#define HeapTupleHeaderSetXminInvalid(tup) \ +( \ + AssertMacro(!HeapTupleHeaderXminCommitted(tup)), \ + ((tup)->t_infomask |= HEAP_XMIN_INVALID) \ +) +#define HeapTupleHeaderSetXminFrozen(tup) \ +( \ + AssertMacro(!HeapTupleHeaderXminInvalid(tup)), \ + ((tup)->t_infomask |= HEAP_XMIN_FROZEN) \ +) + /* * HeapTupleHeaderGetRawXmax gets you the raw Xmax field. To find out the Xid * that updated a tuple, you might need to resolve the MultiXactId if certain @@ -373,7 +405,8 @@ do { \ #define HeapTupleHeaderIsHotUpdated(tup) \ ( \ ((tup)->t_infomask2 & HEAP_HOT_UPDATED) != 0 && \ - ((tup)->t_infomask & (HEAP_XMIN_INVALID | HEAP_XMAX_INVALID)) == 0 \ + ((tup)->t_infomask & HEAP_XMAX_INVALID) == 0 && \ + !HeapTupleHeaderXminInvalid(tup) \ ) #define HeapTupleHeaderSetHotUpdated(tup) \