From c91560defc57f89f7e88632ea14ae77b5cec78ee Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Mon, 21 Jan 2019 17:03:15 -0800 Subject: Move remaining code from tqual.[ch] to heapam.h / heapam_visibility.c. Given these routines are heap specific, and that there will be more generic visibility support in via table AM, it makes sense to move the prototypes to heapam.h (routines like HeapTupleSatisfiesVacuum will not be exposed in a generic fashion, because they are too storage specific). Similarly, the code in tqual.c is specific to heap, so moving it into access/heap/ makes sense. Author: Andres Freund Discussion: https://postgr.es/m/20180703070645.wchpu5muyto5n647@alap3.anarazel.de --- src/backend/access/heap/Makefile | 4 +- src/backend/access/heap/heapam.c | 8 +- src/backend/access/heap/heapam_visibility.c | 1703 +++++++++++++++++++++++ src/backend/access/heap/pruneheap.c | 1 - src/backend/access/heap/rewriteheap.c | 1 - src/backend/access/heap/tuptoaster.c | 1 - src/backend/access/heap/vacuumlazy.c | 1 - src/backend/access/index/genam.c | 1 - src/backend/access/spgist/spgvacuum.c | 2 +- src/backend/access/transam/transam.c | 4 +- src/backend/access/transam/xact.c | 8 +- src/backend/catalog/index.c | 1 - src/backend/commands/analyze.c | 2 +- src/backend/commands/async.c | 2 +- src/backend/commands/cluster.c | 1 - src/backend/executor/nodeBitmapHeapscan.c | 1 - src/backend/executor/nodeModifyTable.c | 1 - src/backend/executor/nodeSamplescan.c | 1 - src/backend/replication/logical/reorderbuffer.c | 4 +- src/backend/replication/logical/snapbuild.c | 2 +- src/backend/storage/ipc/procarray.c | 2 +- src/backend/storage/lmgr/predicate.c | 2 +- src/backend/utils/adt/ri_triggers.c | 2 +- src/backend/utils/cache/inval.c | 6 +- src/backend/utils/time/Makefile | 2 +- src/backend/utils/time/tqual.c | 1703 ----------------------- src/include/access/heapam.h | 34 + src/include/utils/snapshot.h | 3 +- src/include/utils/tqual.h | 56 - 29 files changed, 1763 insertions(+), 1796 deletions(-) create mode 100644 src/backend/access/heap/heapam_visibility.c delete mode 100644 src/backend/utils/time/tqual.c delete mode 100644 src/include/utils/tqual.h (limited to 'src') diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile index 7e7324a9166..eae36fdbf40 100644 --- a/src/backend/access/heap/Makefile +++ b/src/backend/access/heap/Makefile @@ -12,7 +12,7 @@ subdir = src/backend/access/heap top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = heapam.o hio.o pruneheap.o rewriteheap.o syncscan.o tuptoaster.o \ - vacuumlazy.o visibilitymap.o +OBJS = heapam.o heapam_visibility.o hio.o pruneheap.o rewriteheap.o \ + syncscan.o tuptoaster.o vacuumlazy.o visibilitymap.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index dc2ff89d270..b9862247fb0 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -66,7 +66,6 @@ #include "utils/lsyscache.h" #include "utils/relcache.h" #include "utils/snapmgr.h" -#include "utils/tqual.h" /* GUC variable */ @@ -5284,8 +5283,8 @@ test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid, /* * Note: we *must* check TransactionIdIsInProgress before - * TransactionIdDidAbort/Commit; see comment at top of tqual.c for an - * explanation. + * TransactionIdDidAbort/Commit; see comment at top of heapam_visibility.c + * for an explanation. */ if (TransactionIdIsCurrentTransactionId(xid)) { @@ -6254,7 +6253,8 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, * * As with all tuple visibility routines, it's critical to test * TransactionIdIsInProgress before TransactionIdDidCommit, - * because of race conditions explained in detail in tqual.c. + * because of race conditions explained in detail in + * heapam_visibility.c. */ if (TransactionIdIsCurrentTransactionId(xid) || TransactionIdIsInProgress(xid)) diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c new file mode 100644 index 00000000000..86a99943eb6 --- /dev/null +++ b/src/backend/access/heap/heapam_visibility.c @@ -0,0 +1,1703 @@ +/*------------------------------------------------------------------------- + * + * heapam_visibility.c + * Tuple visibility rules for tuples stored in heap. + * + * NOTE: all the HeapTupleSatisfies routines will update the tuple's + * "hint" status bits if we see that the inserting or deleting transaction + * has now committed or aborted (and it is safe to set the hint bits). + * If the hint bits are changed, MarkBufferDirtyHint is called on + * the passed-in buffer. The caller must hold not only a pin, but at least + * shared buffer content lock on the buffer containing the tuple. + * + * NOTE: When using a non-MVCC snapshot, we must check + * TransactionIdIsInProgress (which looks in the PGXACT array) + * before TransactionIdDidCommit/TransactionIdDidAbort (which look in + * pg_xact). Otherwise we have a race condition: we might decide that a + * just-committed transaction crashed, because none of the tests succeed. + * xact.c is careful to record commit/abort in pg_xact before it unsets + * MyPgXact->xid in the PGXACT array. That fixes that problem, but it + * also means there is a window where TransactionIdIsInProgress and + * TransactionIdDidCommit will both return true. If we check only + * TransactionIdDidCommit, we could consider a tuple committed when a + * later GetSnapshotData call will still think the originating transaction + * is in progress, which leads to application-level inconsistency. The + * upshot is that we gotta check TransactionIdIsInProgress first in all + * code paths, except for a few cases where we are looking at + * subtransactions of our own main transaction and so there can't be any + * race condition. + * + * When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than + * TransactionIdIsInProgress, but the logic is otherwise the same: do not + * check pg_xact until after deciding that the xact is no longer in progress. + * + * + * Summary of visibility functions: + * + * HeapTupleSatisfiesMVCC() + * visible to supplied snapshot, excludes current command + * HeapTupleSatisfiesUpdate() + * visible to instant snapshot, with user-supplied command + * counter and more complex result + * HeapTupleSatisfiesSelf() + * visible to instant snapshot and current command + * HeapTupleSatisfiesDirty() + * like HeapTupleSatisfiesSelf(), but includes open transactions + * HeapTupleSatisfiesVacuum() + * visible to any running transaction, used by VACUUM + * HeapTupleSatisfiesNonVacuumable() + * Snapshot-style API for HeapTupleSatisfiesVacuum + * HeapTupleSatisfiesToast() + * visible unless part of interrupted vacuum, used for TOAST + * HeapTupleSatisfiesAny() + * all tuples are visible + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/access/heap/heapam_visibility.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/heapam.h" +#include "access/htup_details.h" +#include "access/multixact.h" +#include "access/subtrans.h" +#include "access/transam.h" +#include "access/xact.h" +#include "access/xlog.h" +#include "storage/bufmgr.h" +#include "storage/procarray.h" +#include "utils/builtins.h" +#include "utils/combocid.h" +#include "utils/snapmgr.h" + + +/* + * SetHintBits() + * + * Set commit/abort hint bits on a tuple, if appropriate at this time. + * + * It is only safe to set a transaction-committed hint bit if we know the + * transaction's commit record is guaranteed to be flushed to disk before the + * buffer, or if the table is temporary or unlogged and will be obliterated by + * a crash anyway. We cannot change the LSN of the page here, because we may + * hold only a share lock on the buffer, so we can only use the LSN to + * interlock this if the buffer's LSN already is newer than the commit LSN; + * otherwise we have to just refrain from setting the hint bit until some + * future re-examination of the tuple. + * + * We can always set hint bits when marking a transaction aborted. (Some + * code in heapam.c relies on that!) + * + * Also, if we are cleaning up HEAP_MOVED_IN or HEAP_MOVED_OFF entries, then + * we can always set the hint bits, since pre-9.0 VACUUM FULL always used + * synchronous commits and didn't move tuples that weren't previously + * hinted. (This is not known by this subroutine, but is applied by its + * callers.) Note: old-style VACUUM FULL is gone, but we have to keep this + * module's support for MOVED_OFF/MOVED_IN flag bits for as long as we + * support in-place update from pre-9.0 databases. + * + * Normal commits may be asynchronous, so for those we need to get the LSN + * of the transaction and then check whether this is flushed. + * + * The caller should pass xid as the XID of the transaction to check, or + * InvalidTransactionId if no check is needed. + */ +static inline void +SetHintBits(HeapTupleHeader tuple, Buffer buffer, + uint16 infomask, TransactionId xid) +{ + if (TransactionIdIsValid(xid)) + { + /* NB: xid must be known committed here! */ + XLogRecPtr commitLSN = TransactionIdGetCommitLSN(xid); + + if (BufferIsPermanent(buffer) && XLogNeedsFlush(commitLSN) && + BufferGetLSNAtomic(buffer) < commitLSN) + { + /* not flushed and no LSN interlock, so don't set hint */ + return; + } + } + + tuple->t_infomask |= infomask; + MarkBufferDirtyHint(buffer, true); +} + +/* + * HeapTupleSetHintBits --- exported version of SetHintBits() + * + * This must be separate because of C99's brain-dead notions about how to + * implement inline functions. + */ +void +HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer, + uint16 infomask, TransactionId xid) +{ + SetHintBits(tuple, buffer, infomask, xid); +} + + +/* + * HeapTupleSatisfiesSelf + * True iff heap tuple is valid "for itself". + * + * See SNAPSHOT_MVCC's definition for the intended behaviour. + * + * Note: + * Assumes heap tuple is valid. + * + * The satisfaction of "itself" requires the following: + * + * ((Xmin == my-transaction && the row was updated by the current transaction, and + * (Xmax is null it was not deleted + * [|| Xmax != my-transaction)]) [or it was deleted by another transaction] + * || + * + * (Xmin is committed && the row was modified by a committed transaction, and + * (Xmax is null || the row has not been deleted, or + * (Xmax != my-transaction && the row was deleted by another transaction + * Xmax is not committed))) that has not been committed + */ +static bool +HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer) +{ + HeapTupleHeader tuple = htup->t_data; + + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + + if (!HeapTupleHeaderXminCommitted(tuple)) + { + if (HeapTupleHeaderXminInvalid(tuple)) + return false; + + /* Used by pre-9.0 binary upgrades */ + if (tuple->t_infomask & HEAP_MOVED_OFF) + { + TransactionId xvac = HeapTupleHeaderGetXvac(tuple); + + if (TransactionIdIsCurrentTransactionId(xvac)) + return false; + if (!TransactionIdIsInProgress(xvac)) + { + if (TransactionIdDidCommit(xvac)) + { + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return false; + } + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + InvalidTransactionId); + } + } + /* Used by pre-9.0 binary upgrades */ + else if (tuple->t_infomask & HEAP_MOVED_IN) + { + TransactionId xvac = HeapTupleHeaderGetXvac(tuple); + + if (!TransactionIdIsCurrentTransactionId(xvac)) + { + if (TransactionIdIsInProgress(xvac)) + return false; + if (TransactionIdDidCommit(xvac)) + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + InvalidTransactionId); + else + { + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return false; + } + } + } + else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple))) + { + if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */ + return true; + + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */ + return true; + + if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) + { + TransactionId xmax; + + xmax = HeapTupleGetUpdateXid(tuple); + + /* not LOCKED_ONLY, so it has to have an xmax */ + Assert(TransactionIdIsValid(xmax)); + + /* updating subtransaction must have aborted */ + if (!TransactionIdIsCurrentTransactionId(xmax)) + return true; + else + return false; + } + + if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) + { + /* deleting subtransaction must have aborted */ + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + return true; + } + + return false; + } + else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple))) + return false; + else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple))) + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + HeapTupleHeaderGetRawXmin(tuple)); + else + { + /* it must have aborted or crashed */ + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return false; + } + } + + /* by here, the inserting transaction has committed */ + + if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */ + return true; + + if (tuple->t_infomask & HEAP_XMAX_COMMITTED) + { + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + return true; + return false; /* updated by other */ + } + + if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) + { + TransactionId xmax; + + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + return true; + + xmax = HeapTupleGetUpdateXid(tuple); + + /* not LOCKED_ONLY, so it has to have an xmax */ + Assert(TransactionIdIsValid(xmax)); + + if (TransactionIdIsCurrentTransactionId(xmax)) + return false; + if (TransactionIdIsInProgress(xmax)) + return true; + if (TransactionIdDidCommit(xmax)) + return false; + /* it must have aborted or crashed */ + return true; + } + + if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) + { + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + return true; + return false; + } + + if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple))) + return true; + + if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple))) + { + /* it must have aborted or crashed */ + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + return true; + } + + /* xmax transaction committed */ + + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + { + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + return true; + } + + SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, + HeapTupleHeaderGetRawXmax(tuple)); + return false; +} + +/* + * HeapTupleSatisfiesAny + * Dummy "satisfies" routine: any tuple satisfies SnapshotAny. + */ +static bool +HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer) +{ + return true; +} + +/* + * HeapTupleSatisfiesToast + * True iff heap tuple is valid as a TOAST row. + * + * See SNAPSHOT_TOAST's definition for the intended behaviour. + * + * This is a simplified version that only checks for VACUUM moving conditions. + * It's appropriate for TOAST usage because TOAST really doesn't want to do + * its own time qual checks; if you can see the main table row that contains + * a TOAST reference, you should be able to see the TOASTed value. However, + * vacuuming a TOAST table is independent of the main table, and in case such + * a vacuum fails partway through, we'd better do this much checking. + * + * Among other things, this means you can't do UPDATEs of rows in a TOAST + * table. + */ +static bool +HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot, + Buffer buffer) +{ + HeapTupleHeader tuple = htup->t_data; + + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + + if (!HeapTupleHeaderXminCommitted(tuple)) + { + if (HeapTupleHeaderXminInvalid(tuple)) + return false; + + /* Used by pre-9.0 binary upgrades */ + if (tuple->t_infomask & HEAP_MOVED_OFF) + { + TransactionId xvac = HeapTupleHeaderGetXvac(tuple); + + if (TransactionIdIsCurrentTransactionId(xvac)) + return false; + if (!TransactionIdIsInProgress(xvac)) + { + if (TransactionIdDidCommit(xvac)) + { + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return false; + } + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + InvalidTransactionId); + } + } + /* Used by pre-9.0 binary upgrades */ + else if (tuple->t_infomask & HEAP_MOVED_IN) + { + TransactionId xvac = HeapTupleHeaderGetXvac(tuple); + + if (!TransactionIdIsCurrentTransactionId(xvac)) + { + if (TransactionIdIsInProgress(xvac)) + return false; + if (TransactionIdDidCommit(xvac)) + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + InvalidTransactionId); + else + { + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return false; + } + } + } + + /* + * An invalid Xmin can be left behind by a speculative insertion that + * is canceled by super-deleting the tuple. This also applies to + * TOAST tuples created during speculative insertion. + */ + else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple))) + return false; + } + + /* otherwise assume the tuple is valid for TOAST. */ + return true; +} + +/* + * HeapTupleSatisfiesUpdate + * + * This function returns a more detailed result code than most of the + * functions in this file, since UPDATE needs to know more than "is it + * visible?". It also allows for user-supplied CommandId rather than + * relying on CurrentCommandId. + * + * The possible return codes are: + * + * HeapTupleInvisible: the tuple didn't exist at all when the scan started, + * e.g. it was created by a later CommandId. + * + * HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be + * updated. + * + * HeapTupleSelfUpdated: The tuple was updated by the current transaction, + * after the current scan started. + * + * HeapTupleUpdated: The tuple was updated by a committed transaction. + * + * HeapTupleBeingUpdated: The tuple is being updated by an in-progress + * transaction other than the current transaction. (Note: this includes + * the case where the tuple is share-locked by a MultiXact, even if the + * MultiXact includes the current transaction. Callers that want to + * distinguish that case must test for it themselves.) + */ +HTSU_Result +HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, + Buffer buffer) +{ + HeapTupleHeader tuple = htup->t_data; + + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + + if (!HeapTupleHeaderXminCommitted(tuple)) + { + if (HeapTupleHeaderXminInvalid(tuple)) + return HeapTupleInvisible; + + /* Used by pre-9.0 binary upgrades */ + if (tuple->t_infomask & HEAP_MOVED_OFF) + { + TransactionId xvac = HeapTupleHeaderGetXvac(tuple); + + if (TransactionIdIsCurrentTransactionId(xvac)) + return HeapTupleInvisible; + if (!TransactionIdIsInProgress(xvac)) + { + if (TransactionIdDidCommit(xvac)) + { + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return HeapTupleInvisible; + } + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + InvalidTransactionId); + } + } + /* Used by pre-9.0 binary upgrades */ + else if (tuple->t_infomask & HEAP_MOVED_IN) + { + TransactionId xvac = HeapTupleHeaderGetXvac(tuple); + + if (!TransactionIdIsCurrentTransactionId(xvac)) + { + if (TransactionIdIsInProgress(xvac)) + return HeapTupleInvisible; + if (TransactionIdDidCommit(xvac)) + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + InvalidTransactionId); + else + { + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return HeapTupleInvisible; + } + } + } + else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple))) + { + if (HeapTupleHeaderGetCmin(tuple) >= curcid) + return HeapTupleInvisible; /* inserted after scan started */ + + if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */ + return HeapTupleMayBeUpdated; + + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + { + TransactionId xmax; + + xmax = HeapTupleHeaderGetRawXmax(tuple); + + /* + * Careful here: even though this tuple was created by our own + * transaction, it might be locked by other transactions, if + * the original version was key-share locked when we updated + * it. + */ + + if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) + { + if (MultiXactIdIsRunning(xmax, true)) + return HeapTupleBeingUpdated; + else + return HeapTupleMayBeUpdated; + } + + /* + * If the locker is gone, then there is nothing of interest + * left in this Xmax; otherwise, report the tuple as + * locked/updated. + */ + if (!TransactionIdIsInProgress(xmax)) + return HeapTupleMayBeUpdated; + return HeapTupleBeingUpdated; + } + + if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) + { + TransactionId xmax; + + xmax = HeapTupleGetUpdateXid(tuple); + + /* not LOCKED_ONLY, so it has to have an xmax */ + Assert(TransactionIdIsValid(xmax)); + + /* deleting subtransaction must have aborted */ + if (!TransactionIdIsCurrentTransactionId(xmax)) + { + if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), + false)) + return HeapTupleBeingUpdated; + return HeapTupleMayBeUpdated; + } + else + { + if (HeapTupleHeaderGetCmax(tuple) >= curcid) + return HeapTupleSelfUpdated; /* updated after scan + * started */ + else + return HeapTupleInvisible; /* updated before scan + * started */ + } + } + + if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) + { + /* deleting subtransaction must have aborted */ + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + return HeapTupleMayBeUpdated; + } + + if (HeapTupleHeaderGetCmax(tuple) >= curcid) + return HeapTupleSelfUpdated; /* updated after scan started */ + else + return HeapTupleInvisible; /* updated before scan started */ + } + else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple))) + return HeapTupleInvisible; + else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple))) + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + HeapTupleHeaderGetRawXmin(tuple)); + else + { + /* it must have aborted or crashed */ + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return HeapTupleInvisible; + } + } + + /* by here, the inserting transaction has committed */ + + if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */ + return HeapTupleMayBeUpdated; + + if (tuple->t_infomask & HEAP_XMAX_COMMITTED) + { + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + return HeapTupleMayBeUpdated; + return HeapTupleUpdated; /* updated by other */ + } + + if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) + { + TransactionId xmax; + + if (HEAP_LOCKED_UPGRADED(tuple->t_infomask)) + return HeapTupleMayBeUpdated; + + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + { + if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true)) + return HeapTupleBeingUpdated; + + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); + return HeapTupleMayBeUpdated; + } + + xmax = HeapTupleGetUpdateXid(tuple); + if (!TransactionIdIsValid(xmax)) + { + if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false)) + return HeapTupleBeingUpdated; + } + + /* not LOCKED_ONLY, so it has to have an xmax */ + Assert(TransactionIdIsValid(xmax)); + + if (TransactionIdIsCurrentTransactionId(xmax)) + { + if (HeapTupleHeaderGetCmax(tuple) >= curcid) + return HeapTupleSelfUpdated; /* updated after scan started */ + else + return HeapTupleInvisible; /* updated before scan started */ + } + + if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false)) + return HeapTupleBeingUpdated; + + if (TransactionIdDidCommit(xmax)) + return HeapTupleUpdated; + + /* + * By here, the update in the Xmax is either aborted or crashed, but + * what about the other members? + */ + + if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false)) + { + /* + * There's no member, even just a locker, alive anymore, so we can + * mark the Xmax as invalid. + */ + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + return HeapTupleMayBeUpdated; + } + else + { + /* There are lockers running */ + return HeapTupleBeingUpdated; + } + } + + if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) + { + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + return HeapTupleBeingUpdated; + if (HeapTupleHeaderGetCmax(tuple) >= curcid) + return HeapTupleSelfUpdated; /* updated after scan started */ + else + return HeapTupleInvisible; /* updated before scan started */ + } + + if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple))) + return HeapTupleBeingUpdated; + + if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple))) + { + /* it must have aborted or crashed */ + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + return HeapTupleMayBeUpdated; + } + + /* xmax transaction committed */ + + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + { + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + return HeapTupleMayBeUpdated; + } + + SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, + HeapTupleHeaderGetRawXmax(tuple)); + return HeapTupleUpdated; /* updated by other */ +} + +/* + * HeapTupleSatisfiesDirty + * True iff heap tuple is valid including effects of open transactions. + * + * See SNAPSHOT_DIRTY's definition for the intended behaviour. + * + * This is essentially like HeapTupleSatisfiesSelf as far as effects of + * the current transaction and committed/aborted xacts are concerned. + * However, we also include the effects of other xacts still in progress. + * + * A special hack is that the passed-in snapshot struct is used as an + * output argument to return the xids of concurrent xacts that affected the + * tuple. snapshot->xmin is set to the tuple's xmin if that is another + * transaction that's still in progress; or to InvalidTransactionId if the + * tuple's xmin is committed good, committed dead, or my own xact. + * Similarly for snapshot->xmax and the tuple's xmax. If the tuple was + * inserted speculatively, meaning that the inserter might still back down + * on the insertion without aborting the whole transaction, the associated + * token is also returned in snapshot->speculativeToken. + */ +static bool +HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot, + Buffer buffer) +{ + HeapTupleHeader tuple = htup->t_data; + + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + + snapshot->xmin = snapshot->xmax = InvalidTransactionId; + snapshot->speculativeToken = 0; + + if (!HeapTupleHeaderXminCommitted(tuple)) + { + if (HeapTupleHeaderXminInvalid(tuple)) + return false; + + /* Used by pre-9.0 binary upgrades */ + if (tuple->t_infomask & HEAP_MOVED_OFF) + { + TransactionId xvac = HeapTupleHeaderGetXvac(tuple); + + if (TransactionIdIsCurrentTransactionId(xvac)) + return false; + if (!TransactionIdIsInProgress(xvac)) + { + if (TransactionIdDidCommit(xvac)) + { + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return false; + } + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + InvalidTransactionId); + } + } + /* Used by pre-9.0 binary upgrades */ + else if (tuple->t_infomask & HEAP_MOVED_IN) + { + TransactionId xvac = HeapTupleHeaderGetXvac(tuple); + + if (!TransactionIdIsCurrentTransactionId(xvac)) + { + if (TransactionIdIsInProgress(xvac)) + return false; + if (TransactionIdDidCommit(xvac)) + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + InvalidTransactionId); + else + { + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return false; + } + } + } + else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple))) + { + if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */ + return true; + + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */ + return true; + + if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) + { + TransactionId xmax; + + xmax = HeapTupleGetUpdateXid(tuple); + + /* not LOCKED_ONLY, so it has to have an xmax */ + Assert(TransactionIdIsValid(xmax)); + + /* updating subtransaction must have aborted */ + if (!TransactionIdIsCurrentTransactionId(xmax)) + return true; + else + return false; + } + + if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) + { + /* deleting subtransaction must have aborted */ + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + return true; + } + + return false; + } + else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple))) + { + /* + * Return the speculative token to caller. Caller can worry about + * xmax, since it requires a conclusively locked row version, and + * a concurrent update to this tuple is a conflict of its + * purposes. + */ + if (HeapTupleHeaderIsSpeculative(tuple)) + { + snapshot->speculativeToken = + HeapTupleHeaderGetSpeculativeToken(tuple); + + Assert(snapshot->speculativeToken != 0); + } + + snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple); + /* XXX shouldn't we fall through to look at xmax? */ + return true; /* in insertion by other */ + } + else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple))) + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + HeapTupleHeaderGetRawXmin(tuple)); + else + { + /* it must have aborted or crashed */ + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return false; + } + } + + /* by here, the inserting transaction has committed */ + + if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */ + return true; + + if (tuple->t_infomask & HEAP_XMAX_COMMITTED) + { + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + return true; + return false; /* updated by other */ + } + + if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) + { + TransactionId xmax; + + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + return true; + + xmax = HeapTupleGetUpdateXid(tuple); + + /* not LOCKED_ONLY, so it has to have an xmax */ + Assert(TransactionIdIsValid(xmax)); + + if (TransactionIdIsCurrentTransactionId(xmax)) + return false; + if (TransactionIdIsInProgress(xmax)) + { + snapshot->xmax = xmax; + return true; + } + if (TransactionIdDidCommit(xmax)) + return false; + /* it must have aborted or crashed */ + return true; + } + + if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) + { + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + return true; + return false; + } + + if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple))) + { + if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple); + return true; + } + + if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple))) + { + /* it must have aborted or crashed */ + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + return true; + } + + /* xmax transaction committed */ + + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + { + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + return true; + } + + SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, + HeapTupleHeaderGetRawXmax(tuple)); + return false; /* updated by other */ +} + +/* + * HeapTupleSatisfiesMVCC + * True iff heap tuple is valid for the given MVCC snapshot. + * + * See SNAPSHOT_MVCC's definition for the intended behaviour. + * + * Notice that here, we will not update the tuple status hint bits if the + * inserting/deleting transaction is still running according to our snapshot, + * even if in reality it's committed or aborted by now. This is intentional. + * Checking the true transaction state would require access to high-traffic + * shared data structures, creating contention we'd rather do without, and it + * would not change the result of our visibility check anyway. The hint bits + * will be updated by the first visitor that has a snapshot new enough to see + * the inserting/deleting transaction as done. In the meantime, the cost of + * leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC + * call will need to run TransactionIdIsCurrentTransactionId in addition to + * XidInMVCCSnapshot (but it would have to do the latter anyway). In the old + * coding where we tried to set the hint bits as soon as possible, we instead + * did TransactionIdIsInProgress in each call --- to no avail, as long as the + * inserting/deleting transaction was still running --- which was more cycles + * and more contention on the PGXACT array. + */ +static bool +HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot, + Buffer buffer) +{ + HeapTupleHeader tuple = htup->t_data; + + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + + if (!HeapTupleHeaderXminCommitted(tuple)) + { + if (HeapTupleHeaderXminInvalid(tuple)) + return false; + + /* Used by pre-9.0 binary upgrades */ + if (tuple->t_infomask & HEAP_MOVED_OFF) + { + TransactionId xvac = HeapTupleHeaderGetXvac(tuple); + + if (TransactionIdIsCurrentTransactionId(xvac)) + return false; + if (!XidInMVCCSnapshot(xvac, snapshot)) + { + if (TransactionIdDidCommit(xvac)) + { + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return false; + } + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + InvalidTransactionId); + } + } + /* Used by pre-9.0 binary upgrades */ + else if (tuple->t_infomask & HEAP_MOVED_IN) + { + TransactionId xvac = HeapTupleHeaderGetXvac(tuple); + + if (!TransactionIdIsCurrentTransactionId(xvac)) + { + if (XidInMVCCSnapshot(xvac, snapshot)) + return false; + if (TransactionIdDidCommit(xvac)) + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + InvalidTransactionId); + else + { + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return false; + } + } + } + else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple))) + { + if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid) + return false; /* inserted after scan started */ + + if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */ + return true; + + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */ + return true; + + if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) + { + TransactionId xmax; + + xmax = HeapTupleGetUpdateXid(tuple); + + /* not LOCKED_ONLY, so it has to have an xmax */ + Assert(TransactionIdIsValid(xmax)); + + /* updating subtransaction must have aborted */ + if (!TransactionIdIsCurrentTransactionId(xmax)) + return true; + else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid) + return true; /* updated after scan started */ + else + return false; /* updated before scan started */ + } + + if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) + { + /* deleting subtransaction must have aborted */ + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + return true; + } + + if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid) + return true; /* deleted after scan started */ + else + return false; /* deleted before scan started */ + } + else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot)) + return false; + else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple))) + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + HeapTupleHeaderGetRawXmin(tuple)); + else + { + /* it must have aborted or crashed */ + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return false; + } + } + else + { + /* xmin is committed, but maybe not according to our snapshot */ + if (!HeapTupleHeaderXminFrozen(tuple) && + XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot)) + return false; /* treat as still in progress */ + } + + /* by here, the inserting transaction has committed */ + + if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */ + return true; + + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + return true; + + if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) + { + TransactionId xmax; + + /* already checked above */ + Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)); + + xmax = HeapTupleGetUpdateXid(tuple); + + /* not LOCKED_ONLY, so it has to have an xmax */ + Assert(TransactionIdIsValid(xmax)); + + if (TransactionIdIsCurrentTransactionId(xmax)) + { + if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid) + return true; /* deleted after scan started */ + else + return false; /* deleted before scan started */ + } + if (XidInMVCCSnapshot(xmax, snapshot)) + return true; + if (TransactionIdDidCommit(xmax)) + return false; /* updating transaction committed */ + /* it must have aborted or crashed */ + return true; + } + + if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) + { + if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) + { + if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid) + return true; /* deleted after scan started */ + else + return false; /* deleted before scan started */ + } + + if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot)) + return true; + + if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple))) + { + /* it must have aborted or crashed */ + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + return true; + } + + /* xmax transaction committed */ + SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, + HeapTupleHeaderGetRawXmax(tuple)); + } + else + { + /* xmax is committed, but maybe not according to our snapshot */ + if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot)) + return true; /* treat as still in progress */ + } + + /* xmax transaction committed */ + + return false; +} + + +/* + * HeapTupleSatisfiesVacuum + * + * Determine the status of tuples for VACUUM purposes. Here, what + * we mainly want to know is if a tuple is potentially visible to *any* + * running transaction. If so, it can't be removed yet by VACUUM. + * + * OldestXmin is a cutoff XID (obtained from GetOldestXmin()). Tuples + * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might + * still be visible to some open transaction, so we can't remove them, + * even if we see that the deleting transaction has committed. + */ +HTSV_Result +HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin, + Buffer buffer) +{ + HeapTupleHeader tuple = htup->t_data; + + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + + /* + * Has inserting transaction committed? + * + * If the inserting transaction aborted, then the tuple was never visible + * to any other transaction, so we can delete it immediately. + */ + if (!HeapTupleHeaderXminCommitted(tuple)) + { + if (HeapTupleHeaderXminInvalid(tuple)) + return HEAPTUPLE_DEAD; + /* Used by pre-9.0 binary upgrades */ + else if (tuple->t_infomask & HEAP_MOVED_OFF) + { + TransactionId xvac = HeapTupleHeaderGetXvac(tuple); + + if (TransactionIdIsCurrentTransactionId(xvac)) + return HEAPTUPLE_DELETE_IN_PROGRESS; + if (TransactionIdIsInProgress(xvac)) + return HEAPTUPLE_DELETE_IN_PROGRESS; + if (TransactionIdDidCommit(xvac)) + { + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return HEAPTUPLE_DEAD; + } + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + InvalidTransactionId); + } + /* Used by pre-9.0 binary upgrades */ + else if (tuple->t_infomask & HEAP_MOVED_IN) + { + TransactionId xvac = HeapTupleHeaderGetXvac(tuple); + + if (TransactionIdIsCurrentTransactionId(xvac)) + return HEAPTUPLE_INSERT_IN_PROGRESS; + if (TransactionIdIsInProgress(xvac)) + return HEAPTUPLE_INSERT_IN_PROGRESS; + if (TransactionIdDidCommit(xvac)) + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + InvalidTransactionId); + else + { + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return HEAPTUPLE_DEAD; + } + } + else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple))) + { + if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */ + return HEAPTUPLE_INSERT_IN_PROGRESS; + /* only locked? run infomask-only check first, for performance */ + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) || + HeapTupleHeaderIsOnlyLocked(tuple)) + return HEAPTUPLE_INSERT_IN_PROGRESS; + /* inserted and then deleted by same xact */ + if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple))) + return HEAPTUPLE_DELETE_IN_PROGRESS; + /* deleting subtransaction must have aborted */ + return HEAPTUPLE_INSERT_IN_PROGRESS; + } + else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple))) + { + /* + * It'd be possible to discern between INSERT/DELETE in progress + * here by looking at xmax - but that doesn't seem beneficial for + * the majority of callers and even detrimental for some. We'd + * rather have callers look at/wait for xmin than xmax. It's + * always correct to return INSERT_IN_PROGRESS because that's + * what's happening from the view of other backends. + */ + return HEAPTUPLE_INSERT_IN_PROGRESS; + } + else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple))) + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, + HeapTupleHeaderGetRawXmin(tuple)); + else + { + /* + * Not in Progress, Not Committed, so either Aborted or crashed + */ + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, + InvalidTransactionId); + return HEAPTUPLE_DEAD; + } + + /* + * At this point the xmin is known committed, but we might not have + * been able to set the hint bit yet; so we can no longer Assert that + * it's set. + */ + } + + /* + * Okay, the inserter committed, so it was good at some point. Now what + * about the deleting transaction? + */ + if (tuple->t_infomask & HEAP_XMAX_INVALID) + return HEAPTUPLE_LIVE; + + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + { + /* + * "Deleting" xact really only locked it, so the tuple is live in any + * case. However, we should make sure that either XMAX_COMMITTED or + * XMAX_INVALID gets set once the xact is gone, to reduce the costs of + * examining the tuple for future xacts. + */ + if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) + { + if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) + { + /* + * If it's a pre-pg_upgrade tuple, the multixact cannot + * possibly be running; otherwise have to check. + */ + if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) && + MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), + true)) + return HEAPTUPLE_LIVE; + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); + } + else + { + if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple))) + return HEAPTUPLE_LIVE; + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + } + } + + /* + * We don't really care whether xmax did commit, abort or crash. We + * know that xmax did lock the tuple, but it did not and will never + * actually update it. + */ + + return HEAPTUPLE_LIVE; + } + + if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) + { + TransactionId xmax = HeapTupleGetUpdateXid(tuple); + + /* already checked above */ + Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)); + + /* not LOCKED_ONLY, so it has to have an xmax */ + Assert(TransactionIdIsValid(xmax)); + + if (TransactionIdIsInProgress(xmax)) + return HEAPTUPLE_DELETE_IN_PROGRESS; + else if (TransactionIdDidCommit(xmax)) + { + /* + * The multixact might still be running due to lockers. If the + * updater is below the xid horizon, we have to return DEAD + * regardless -- otherwise we could end up with a tuple where the + * updater has to be removed due to the horizon, but is not pruned + * away. It's not a problem to prune that tuple, because any + * remaining lockers will also be present in newer tuple versions. + */ + if (!TransactionIdPrecedes(xmax, OldestXmin)) + return HEAPTUPLE_RECENTLY_DEAD; + + return HEAPTUPLE_DEAD; + } + else if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false)) + { + /* + * Not in Progress, Not Committed, so either Aborted or crashed. + * Mark the Xmax as invalid. + */ + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); + } + + return HEAPTUPLE_LIVE; + } + + if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) + { + if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple))) + return HEAPTUPLE_DELETE_IN_PROGRESS; + else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple))) + SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, + HeapTupleHeaderGetRawXmax(tuple)); + else + { + /* + * Not in Progress, Not Committed, so either Aborted or crashed + */ + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, + InvalidTransactionId); + return HEAPTUPLE_LIVE; + } + + /* + * At this point the xmax is known committed, but we might not have + * been able to set the hint bit yet; so we can no longer Assert that + * it's set. + */ + } + + /* + * Deleter committed, but perhaps it was recent enough that some open + * transactions could still see the tuple. + */ + if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin)) + return HEAPTUPLE_RECENTLY_DEAD; + + /* Otherwise, it's dead and removable */ + return HEAPTUPLE_DEAD; +} + + +/* + * HeapTupleSatisfiesNonVacuumable + * + * True if tuple might be visible to some transaction; false if it's + * surely dead to everyone, ie, vacuumable. + * + * See SNAPSHOT_TOAST's definition for the intended behaviour. + * + * This is an interface to HeapTupleSatisfiesVacuum that's callable via + * HeapTupleSatisfiesSnapshot, so it can be used through a Snapshot. + * snapshot->xmin must have been set up with the xmin horizon to use. + */ +static bool +HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot, + Buffer buffer) +{ + return HeapTupleSatisfiesVacuum(htup, snapshot->xmin, buffer) + != HEAPTUPLE_DEAD; +} + + +/* + * HeapTupleIsSurelyDead + * + * Cheaply determine whether a tuple is surely dead to all onlookers. + * We sometimes use this in lieu of HeapTupleSatisfiesVacuum when the + * tuple has just been tested by another visibility routine (usually + * HeapTupleSatisfiesMVCC) and, therefore, any hint bits that can be set + * should already be set. We assume that if no hint bits are set, the xmin + * or xmax transaction is still running. This is therefore faster than + * HeapTupleSatisfiesVacuum, because we don't consult PGXACT nor CLOG. + * It's okay to return false when in doubt, but we must return true only + * if the tuple is removable. + */ +bool +HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin) +{ + HeapTupleHeader tuple = htup->t_data; + + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + + /* + * If the inserting transaction is marked invalid, then it aborted, and + * the tuple is definitely dead. If it's marked neither committed nor + * invalid, then we assume it's still alive (since the presumption is that + * all relevant hint bits were just set moments ago). + */ + if (!HeapTupleHeaderXminCommitted(tuple)) + return HeapTupleHeaderXminInvalid(tuple) ? true : false; + + /* + * If the inserting transaction committed, but any deleting transaction + * aborted, the tuple is still alive. + */ + if (tuple->t_infomask & HEAP_XMAX_INVALID) + return false; + + /* + * If the XMAX is just a lock, the tuple is still alive. + */ + if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + return false; + + /* + * If the Xmax is a MultiXact, it might be dead or alive, but we cannot + * know without checking pg_multixact. + */ + if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) + return false; + + /* If deleter isn't known to have committed, assume it's still running. */ + if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) + return false; + + /* Deleter committed, so tuple is dead if the XID is old enough. */ + return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin); +} + +/* + * Is the tuple really only locked? That is, is it not updated? + * + * It's easy to check just infomask bits if the locker is not a multi; but + * otherwise we need to verify that the updating transaction has not aborted. + * + * This function is here because it follows the same time qualification rules + * laid out at the top of this file. + */ +bool +HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple) +{ + TransactionId xmax; + + /* if there's no valid Xmax, then there's obviously no update either */ + if (tuple->t_infomask & HEAP_XMAX_INVALID) + return true; + + if (tuple->t_infomask & HEAP_XMAX_LOCK_ONLY) + return true; + + /* invalid xmax means no update */ + if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple))) + return true; + + /* + * if HEAP_XMAX_LOCK_ONLY is not set and not a multi, then this must + * necessarily have been updated + */ + if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI)) + return false; + + /* ... but if it's a multi, then perhaps the updating Xid aborted. */ + xmax = HeapTupleGetUpdateXid(tuple); + + /* not LOCKED_ONLY, so it has to have an xmax */ + Assert(TransactionIdIsValid(xmax)); + + if (TransactionIdIsCurrentTransactionId(xmax)) + return false; + if (TransactionIdIsInProgress(xmax)) + return false; + if (TransactionIdDidCommit(xmax)) + return false; + + /* + * not current, not in progress, not committed -- must have aborted or + * crashed + */ + return true; +} + +/* + * check whether the transaction id 'xid' is in the pre-sorted array 'xip'. + */ +static bool +TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num) +{ + return bsearch(&xid, xip, num, + sizeof(TransactionId), xidComparator) != NULL; +} + +/* + * See the comments for HeapTupleSatisfiesMVCC for the semantics this function + * obeys. + * + * Only usable on tuples from catalog tables! + * + * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support + * reading catalog pages which couldn't have been created in an older version. + * + * We don't set any hint bits in here as it seems unlikely to be beneficial as + * those should already be set by normal access and it seems to be too + * dangerous to do so as the semantics of doing so during timetravel are more + * complicated than when dealing "only" with the present. + */ +static bool +HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot, + Buffer buffer) +{ + HeapTupleHeader tuple = htup->t_data; + TransactionId xmin = HeapTupleHeaderGetXmin(tuple); + TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple); + + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + + /* inserting transaction aborted */ + if (HeapTupleHeaderXminInvalid(tuple)) + { + Assert(!TransactionIdDidCommit(xmin)); + return false; + } + /* check if it's one of our txids, toplevel is also in there */ + else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt)) + { + bool resolved; + CommandId cmin = HeapTupleHeaderGetRawCommandId(tuple); + CommandId cmax = InvalidCommandId; + + /* + * another transaction might have (tried to) delete this tuple or + * cmin/cmax was stored in a combocid. So we need to lookup the actual + * values externally. + */ + resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot, + htup, buffer, + &cmin, &cmax); + + if (!resolved) + elog(ERROR, "could not resolve cmin/cmax of catalog tuple"); + + Assert(cmin != InvalidCommandId); + + if (cmin >= snapshot->curcid) + return false; /* inserted after scan started */ + /* fall through */ + } + /* committed before our xmin horizon. Do a normal visibility check. */ + else if (TransactionIdPrecedes(xmin, snapshot->xmin)) + { + Assert(!(HeapTupleHeaderXminCommitted(tuple) && + !TransactionIdDidCommit(xmin))); + + /* check for hint bit first, consult clog afterwards */ + if (!HeapTupleHeaderXminCommitted(tuple) && + !TransactionIdDidCommit(xmin)) + return false; + /* fall through */ + } + /* beyond our xmax horizon, i.e. invisible */ + else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax)) + { + return false; + } + /* check if it's a committed transaction in [xmin, xmax) */ + else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt)) + { + /* fall through */ + } + + /* + * none of the above, i.e. between [xmin, xmax) but hasn't committed. I.e. + * invisible. + */ + else + { + return false; + } + + /* at this point we know xmin is visible, go on to check xmax */ + + /* xid invalid or aborted */ + if (tuple->t_infomask & HEAP_XMAX_INVALID) + return true; + /* locked tuples are always visible */ + else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) + return true; + + /* + * We can see multis here if we're looking at user tables or if somebody + * SELECT ... FOR SHARE/UPDATE a system table. + */ + else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) + { + xmax = HeapTupleGetUpdateXid(tuple); + } + + /* check if it's one of our txids, toplevel is also in there */ + if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt)) + { + bool resolved; + CommandId cmin; + CommandId cmax = HeapTupleHeaderGetRawCommandId(tuple); + + /* Lookup actual cmin/cmax values */ + resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot, + htup, buffer, + &cmin, &cmax); + + if (!resolved) + elog(ERROR, "could not resolve combocid to cmax"); + + Assert(cmax != InvalidCommandId); + + if (cmax >= snapshot->curcid) + return true; /* deleted after scan started */ + else + return false; /* deleted before scan started */ + } + /* below xmin horizon, normal transaction state is valid */ + else if (TransactionIdPrecedes(xmax, snapshot->xmin)) + { + Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED && + !TransactionIdDidCommit(xmax))); + + /* check hint bit first */ + if (tuple->t_infomask & HEAP_XMAX_COMMITTED) + return false; + + /* check clog */ + return !TransactionIdDidCommit(xmax); + } + /* above xmax horizon, we cannot possibly see the deleting transaction */ + else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax)) + return true; + /* xmax is between [xmin, xmax), check known committed array */ + else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt)) + return false; + /* xmax is between [xmin, xmax), but known not to have committed yet */ + else + return true; +} + +/* + * HeapTupleSatisfiesVisibility + * True iff heap tuple satisfies a time qual. + * + * Notes: + * Assumes heap tuple is valid, and buffer at least share locked. + * + * Hint bits in the HeapTuple's t_infomask may be updated as a side effect; + * if so, the indicated buffer is marked dirty. + */ +bool +HeapTupleSatisfiesVisibility(HeapTuple tup, Snapshot snapshot, Buffer buffer) +{ + switch (snapshot->snapshot_type) + { + case SNAPSHOT_MVCC: + return HeapTupleSatisfiesMVCC(tup, snapshot, buffer); + break; + case SNAPSHOT_SELF: + return HeapTupleSatisfiesSelf(tup, snapshot, buffer); + break; + case SNAPSHOT_ANY: + return HeapTupleSatisfiesAny(tup, snapshot, buffer); + break; + case SNAPSHOT_TOAST: + return HeapTupleSatisfiesToast(tup, snapshot, buffer); + break; + case SNAPSHOT_DIRTY: + return HeapTupleSatisfiesDirty(tup, snapshot, buffer); + break; + case SNAPSHOT_HISTORIC_MVCC: + return HeapTupleSatisfiesHistoricMVCC(tup, snapshot, buffer); + break; + case SNAPSHOT_NON_VACUUMABLE: + return HeapTupleSatisfiesNonVacuumable(tup, snapshot, buffer); + break; + } + + return false; /* keep compiler quiet */ +} diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index 2665f24730a..a3e51922d85 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -25,7 +25,6 @@ #include "storage/bufmgr.h" #include "utils/snapmgr.h" #include "utils/rel.h" -#include "utils/tqual.h" /* Working data for heap_page_prune and subroutines */ typedef struct diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index f6b0f1b0933..f5cf9ffc9c7 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -130,7 +130,6 @@ #include "utils/memutils.h" #include "utils/rel.h" -#include "utils/tqual.h" #include "storage/procarray.h" diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c index 6763fb920c2..cd921a46005 100644 --- a/src/backend/access/heap/tuptoaster.c +++ b/src/backend/access/heap/tuptoaster.c @@ -42,7 +42,6 @@ #include "utils/rel.h" #include "utils/snapmgr.h" #include "utils/typcache.h" -#include "utils/tqual.h" #undef TOAST_DEBUG diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index c09eb6eff88..37aa484ec3a 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -59,7 +59,6 @@ #include "utils/memutils.h" #include "utils/pg_rusage.h" #include "utils/timestamp.h" -#include "utils/tqual.h" /* diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c index ffd79ffca7d..e0a5ea42d52 100644 --- a/src/backend/access/index/genam.c +++ b/src/backend/access/index/genam.c @@ -35,7 +35,6 @@ #include "utils/ruleutils.h" #include "utils/snapmgr.h" #include "utils/syscache.h" -#include "utils/tqual.h" /* ---------------------------------------------------------------- diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c index ccb5ac402c8..b9311ce5950 100644 --- a/src/backend/access/spgist/spgvacuum.c +++ b/src/backend/access/spgist/spgvacuum.c @@ -192,7 +192,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer, * happened since VACUUM started. * * Note: we could make a tighter test by seeing if the xid is - * "running" according to the active snapshot; but tqual.c doesn't + * "running" according to the active snapshot; but snapmgr.c doesn't * currently export a suitable API, and it's not entirely clear * that a tighter test is worth the cycles anyway. */ diff --git a/src/backend/access/transam/transam.c b/src/backend/access/transam/transam.c index e55611ae09d..365ddfb428f 100644 --- a/src/backend/access/transam/transam.c +++ b/src/backend/access/transam/transam.c @@ -228,8 +228,8 @@ TransactionIdDidAbort(TransactionId transactionId) * (and so it's not named TransactionIdDidComplete, which would be the * appropriate name for a function that worked that way). The intended * use is just to short-circuit TransactionIdIsInProgress calls when doing - * repeated tqual.c checks for the same XID. If this isn't extremely fast - * then it will be counterproductive. + * repeated heapam_visibility.c checks for the same XID. If this isn't + * extremely fast then it will be counterproductive. * * Note: * Assumes transaction identifier is valid. diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 18467d96d28..7c3a9c1e899 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -774,10 +774,10 @@ TransactionIdIsCurrentTransactionId(TransactionId xid) * We always say that BootstrapTransactionId is "not my transaction ID" * even when it is (ie, during bootstrap). Along with the fact that * transam.c always treats BootstrapTransactionId as already committed, - * this causes the tqual.c routines to see all tuples as committed, which - * is what we need during bootstrap. (Bootstrap mode only inserts tuples, - * it never updates or deletes them, so all tuples can be presumed good - * immediately.) + * this causes the heapam_visibility.c routines to see all tuples as + * committed, which is what we need during bootstrap. (Bootstrap mode + * only inserts tuples, it never updates or deletes them, so all tuples + * can be presumed good immediately.) * * Likewise, InvalidTransactionId and FrozenTransactionId are certainly * not my transaction ID, so we can just return "false" immediately for diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index b6d472335fe..7359600fd36 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -75,7 +75,6 @@ #include "utils/syscache.h" #include "utils/tuplesort.h" #include "utils/snapmgr.h" -#include "utils/tqual.h" /* Potentially set by pg_upgrade_support functions */ diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index 83625d10e75..c8192353ebe 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -17,6 +17,7 @@ #include #include "access/genam.h" +#include "access/heapam.h" #include "access/multixact.h" #include "access/relation.h" #include "access/sysattr.h" @@ -63,7 +64,6 @@ #include "utils/sortsupport.h" #include "utils/syscache.h" #include "utils/timestamp.h" -#include "utils/tqual.h" /* Per-index data for ANALYZE */ diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c index 0099b881378..5a7ee0de4cf 100644 --- a/src/backend/commands/async.c +++ b/src/backend/commands/async.c @@ -1955,7 +1955,7 @@ asyncQueueProcessPageEntries(volatile QueuePosition *current, * Note that we must test XidInMVCCSnapshot before we test * TransactionIdDidCommit, else we might return a message from * a transaction that is not yet visible to snapshots; compare - * the comments at the head of tqual.c. + * the comments at the head of heapam_visibility.c. * * Also, while our own xact won't be listed in the snapshot, * we need not check for TransactionIdIsCurrentTransactionId diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 99e25cd50fb..5c0f238c7e2 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -52,7 +52,6 @@ #include "utils/relmapper.h" #include "utils/snapmgr.h" #include "utils/syscache.h" -#include "utils/tqual.h" #include "utils/tuplesort.h" diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c index cd20abc141e..5e74585d5e4 100644 --- a/src/backend/executor/nodeBitmapHeapscan.c +++ b/src/backend/executor/nodeBitmapHeapscan.c @@ -51,7 +51,6 @@ #include "utils/rel.h" #include "utils/spccache.h" #include "utils/snapmgr.h" -#include "utils/tqual.h" static TupleTableSlot *BitmapHeapNext(BitmapHeapScanState *node); diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 241711a81d7..566858c19b3 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -53,7 +53,6 @@ #include "utils/builtins.h" #include "utils/memutils.h" #include "utils/rel.h" -#include "utils/tqual.h" static bool ExecOnConflictUpdate(ModifyTableState *mtstate, diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c index 7d4f17b4e99..da4a65fd30a 100644 --- a/src/backend/executor/nodeSamplescan.c +++ b/src/backend/executor/nodeSamplescan.c @@ -25,7 +25,6 @@ #include "storage/predicate.h" #include "utils/builtins.h" #include "utils/rel.h" -#include "utils/tqual.h" static TupleTableSlot *SampleNext(SampleScanState *node); static void tablesample_init(SampleScanState *scanstate); diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index b79ce5db958..a49e226967c 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -56,6 +56,7 @@ #include #include +#include "access/heapam.h" #include "access/rewriteheap.h" #include "access/transam.h" #include "access/tuptoaster.h" @@ -78,7 +79,6 @@ #include "utils/memutils.h" #include "utils/rel.h" #include "utils/relfilenodemap.h" -#include "utils/tqual.h" /* entry for a hash table we use to map from xid to our transaction state */ @@ -1269,7 +1269,7 @@ ReorderBufferCleanupTXN(ReorderBuffer *rb, ReorderBufferTXN *txn) /* * Build a hash with a (relfilenode, ctid) -> (cmin, cmax) mapping for use by - * tqual.c's HeapTupleSatisfiesHistoricMVCC. + * HeapTupleSatisfiesHistoricMVCC. */ static void ReorderBufferBuildTupleCidHash(ReorderBuffer *rb, ReorderBufferTXN *txn) diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index 2310e9fafe0..2f185f78239 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -1119,7 +1119,7 @@ SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xact * NB: We only increase xmax when a catalog modifying transaction commits * (see SnapBuildCommitTxn). Because of this, xmax can be lower than * xmin, which looks odd but is correct and actually more efficient, since - * we hit fast paths in tqual.c. + * we hit fast paths in heapam_visibility.c. */ builder->xmin = running->oldestRunningXid; diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 43110e57b68..cf93357997c 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -1498,7 +1498,7 @@ GetMaxSnapshotSubxidCount(void) * information may not be available. If we find any overflowed subxid arrays, * we have to mark the snapshot's subxid data as overflowed, and extra work * *may* need to be done to determine what's running (see XidInMVCCSnapshot() - * in tqual.c). + * in heapam_visibility.c). * * We also update the following backend-global variables: * TransactionXmin: the oldest xmin of any snapshot in use in the diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c index a962034753b..6fc11f26f0e 100644 --- a/src/backend/storage/lmgr/predicate.c +++ b/src/backend/storage/lmgr/predicate.c @@ -185,6 +185,7 @@ #include "postgres.h" +#include "access/heapam.h" #include "access/htup_details.h" #include "access/slru.h" #include "access/subtrans.h" @@ -202,7 +203,6 @@ #include "storage/procarray.h" #include "utils/rel.h" #include "utils/snapmgr.h" -#include "utils/tqual.h" /* Uncomment the next line to test the graceful degradation code. */ /* #define TEST_OLDSERXID */ diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 93399f811a5..e1aa3d0044f 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -30,6 +30,7 @@ #include "postgres.h" +#include "access/heapam.h" #include "access/htup_details.h" #include "access/sysattr.h" #include "access/table.h" @@ -57,7 +58,6 @@ #include "utils/rls.h" #include "utils/snapmgr.h" #include "utils/syscache.h" -#include "utils/tqual.h" /* ---------- diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c index b9f698ef2ce..16903c25f45 100644 --- a/src/backend/utils/cache/inval.c +++ b/src/backend/utils/cache/inval.c @@ -8,9 +8,9 @@ * When a tuple is updated or deleted, our standard time qualification rules * consider that it is *still valid* so long as we are in the same command, * ie, until the next CommandCounterIncrement() or transaction commit. - * (See utils/time/tqual.c, and note that system catalogs are generally - * scanned under the most current snapshot available, rather than the - * transaction snapshot.) At the command boundary, the old tuple stops + * (See acces/heap/heapam_visibility.c, and note that system catalogs are + * generally scanned under the most current snapshot available, rather than + * the transaction snapshot.) At the command boundary, the old tuple stops * being valid and the new version, if any, becomes valid. Therefore, * we cannot simply flush a tuple from the system caches during heap_update() * or heap_delete(). The tuple is still good at that point; what's more, diff --git a/src/backend/utils/time/Makefile b/src/backend/utils/time/Makefile index 5a6e6fa4c8e..f17b1c53249 100644 --- a/src/backend/utils/time/Makefile +++ b/src/backend/utils/time/Makefile @@ -12,6 +12,6 @@ subdir = src/backend/utils/time top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = combocid.o tqual.o snapmgr.o +OBJS = combocid.o snapmgr.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c deleted file mode 100644 index 71d7e257773..00000000000 --- a/src/backend/utils/time/tqual.c +++ /dev/null @@ -1,1703 +0,0 @@ -/*------------------------------------------------------------------------- - * - * tqual.c - * POSTGRES "time qualification" code, ie, tuple visibility rules. - * - * NOTE: all the HeapTupleSatisfies routines will update the tuple's - * "hint" status bits if we see that the inserting or deleting transaction - * has now committed or aborted (and it is safe to set the hint bits). - * If the hint bits are changed, MarkBufferDirtyHint is called on - * the passed-in buffer. The caller must hold not only a pin, but at least - * shared buffer content lock on the buffer containing the tuple. - * - * NOTE: When using a non-MVCC snapshot, we must check - * TransactionIdIsInProgress (which looks in the PGXACT array) - * before TransactionIdDidCommit/TransactionIdDidAbort (which look in - * pg_xact). Otherwise we have a race condition: we might decide that a - * just-committed transaction crashed, because none of the tests succeed. - * xact.c is careful to record commit/abort in pg_xact before it unsets - * MyPgXact->xid in the PGXACT array. That fixes that problem, but it - * also means there is a window where TransactionIdIsInProgress and - * TransactionIdDidCommit will both return true. If we check only - * TransactionIdDidCommit, we could consider a tuple committed when a - * later GetSnapshotData call will still think the originating transaction - * is in progress, which leads to application-level inconsistency. The - * upshot is that we gotta check TransactionIdIsInProgress first in all - * code paths, except for a few cases where we are looking at - * subtransactions of our own main transaction and so there can't be any - * race condition. - * - * When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than - * TransactionIdIsInProgress, but the logic is otherwise the same: do not - * check pg_xact until after deciding that the xact is no longer in progress. - * - * - * Summary of visibility functions: - * - * HeapTupleSatisfiesMVCC() - * visible to supplied snapshot, excludes current command - * HeapTupleSatisfiesUpdate() - * visible to instant snapshot, with user-supplied command - * counter and more complex result - * HeapTupleSatisfiesSelf() - * visible to instant snapshot and current command - * HeapTupleSatisfiesDirty() - * like HeapTupleSatisfiesSelf(), but includes open transactions - * HeapTupleSatisfiesVacuum() - * visible to any running transaction, used by VACUUM - * HeapTupleSatisfiesNonVacuumable() - * Snapshot-style API for HeapTupleSatisfiesVacuum - * HeapTupleSatisfiesToast() - * visible unless part of interrupted vacuum, used for TOAST - * HeapTupleSatisfiesAny() - * all tuples are visible - * - * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * IDENTIFICATION - * src/backend/utils/time/tqual.c - * - *------------------------------------------------------------------------- - */ - -#include "postgres.h" - -#include "access/htup_details.h" -#include "access/multixact.h" -#include "access/subtrans.h" -#include "access/transam.h" -#include "access/xact.h" -#include "access/xlog.h" -#include "storage/bufmgr.h" -#include "storage/procarray.h" -#include "utils/builtins.h" -#include "utils/combocid.h" -#include "utils/snapmgr.h" -#include "utils/tqual.h" - - -/* - * SetHintBits() - * - * Set commit/abort hint bits on a tuple, if appropriate at this time. - * - * It is only safe to set a transaction-committed hint bit if we know the - * transaction's commit record is guaranteed to be flushed to disk before the - * buffer, or if the table is temporary or unlogged and will be obliterated by - * a crash anyway. We cannot change the LSN of the page here, because we may - * hold only a share lock on the buffer, so we can only use the LSN to - * interlock this if the buffer's LSN already is newer than the commit LSN; - * otherwise we have to just refrain from setting the hint bit until some - * future re-examination of the tuple. - * - * We can always set hint bits when marking a transaction aborted. (Some - * code in heapam.c relies on that!) - * - * Also, if we are cleaning up HEAP_MOVED_IN or HEAP_MOVED_OFF entries, then - * we can always set the hint bits, since pre-9.0 VACUUM FULL always used - * synchronous commits and didn't move tuples that weren't previously - * hinted. (This is not known by this subroutine, but is applied by its - * callers.) Note: old-style VACUUM FULL is gone, but we have to keep this - * module's support for MOVED_OFF/MOVED_IN flag bits for as long as we - * support in-place update from pre-9.0 databases. - * - * Normal commits may be asynchronous, so for those we need to get the LSN - * of the transaction and then check whether this is flushed. - * - * The caller should pass xid as the XID of the transaction to check, or - * InvalidTransactionId if no check is needed. - */ -static inline void -SetHintBits(HeapTupleHeader tuple, Buffer buffer, - uint16 infomask, TransactionId xid) -{ - if (TransactionIdIsValid(xid)) - { - /* NB: xid must be known committed here! */ - XLogRecPtr commitLSN = TransactionIdGetCommitLSN(xid); - - if (BufferIsPermanent(buffer) && XLogNeedsFlush(commitLSN) && - BufferGetLSNAtomic(buffer) < commitLSN) - { - /* not flushed and no LSN interlock, so don't set hint */ - return; - } - } - - tuple->t_infomask |= infomask; - MarkBufferDirtyHint(buffer, true); -} - -/* - * HeapTupleSetHintBits --- exported version of SetHintBits() - * - * This must be separate because of C99's brain-dead notions about how to - * implement inline functions. - */ -void -HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer, - uint16 infomask, TransactionId xid) -{ - SetHintBits(tuple, buffer, infomask, xid); -} - - -/* - * HeapTupleSatisfiesSelf - * True iff heap tuple is valid "for itself". - * - * See SNAPSHOT_MVCC's definition for the intended behaviour. - * - * Note: - * Assumes heap tuple is valid. - * - * The satisfaction of "itself" requires the following: - * - * ((Xmin == my-transaction && the row was updated by the current transaction, and - * (Xmax is null it was not deleted - * [|| Xmax != my-transaction)]) [or it was deleted by another transaction] - * || - * - * (Xmin is committed && the row was modified by a committed transaction, and - * (Xmax is null || the row has not been deleted, or - * (Xmax != my-transaction && the row was deleted by another transaction - * Xmax is not committed))) that has not been committed - */ -static bool -HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer) -{ - HeapTupleHeader tuple = htup->t_data; - - Assert(ItemPointerIsValid(&htup->t_self)); - Assert(htup->t_tableOid != InvalidOid); - - if (!HeapTupleHeaderXminCommitted(tuple)) - { - if (HeapTupleHeaderXminInvalid(tuple)) - return false; - - /* Used by pre-9.0 binary upgrades */ - if (tuple->t_infomask & HEAP_MOVED_OFF) - { - TransactionId xvac = HeapTupleHeaderGetXvac(tuple); - - if (TransactionIdIsCurrentTransactionId(xvac)) - return false; - if (!TransactionIdIsInProgress(xvac)) - { - if (TransactionIdDidCommit(xvac)) - { - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return false; - } - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - InvalidTransactionId); - } - } - /* Used by pre-9.0 binary upgrades */ - else if (tuple->t_infomask & HEAP_MOVED_IN) - { - TransactionId xvac = HeapTupleHeaderGetXvac(tuple); - - if (!TransactionIdIsCurrentTransactionId(xvac)) - { - if (TransactionIdIsInProgress(xvac)) - return false; - if (TransactionIdDidCommit(xvac)) - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - InvalidTransactionId); - else - { - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return false; - } - } - } - else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple))) - { - if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */ - return true; - - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */ - return true; - - if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) - { - TransactionId xmax; - - xmax = HeapTupleGetUpdateXid(tuple); - - /* not LOCKED_ONLY, so it has to have an xmax */ - Assert(TransactionIdIsValid(xmax)); - - /* updating subtransaction must have aborted */ - if (!TransactionIdIsCurrentTransactionId(xmax)) - return true; - else - return false; - } - - if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) - { - /* deleting subtransaction must have aborted */ - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - return true; - } - - return false; - } - else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple))) - return false; - else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple))) - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - HeapTupleHeaderGetRawXmin(tuple)); - else - { - /* it must have aborted or crashed */ - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return false; - } - } - - /* by here, the inserting transaction has committed */ - - if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */ - return true; - - if (tuple->t_infomask & HEAP_XMAX_COMMITTED) - { - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return true; - return false; /* updated by other */ - } - - if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) - { - TransactionId xmax; - - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return true; - - xmax = HeapTupleGetUpdateXid(tuple); - - /* not LOCKED_ONLY, so it has to have an xmax */ - Assert(TransactionIdIsValid(xmax)); - - if (TransactionIdIsCurrentTransactionId(xmax)) - return false; - if (TransactionIdIsInProgress(xmax)) - return true; - if (TransactionIdDidCommit(xmax)) - return false; - /* it must have aborted or crashed */ - return true; - } - - if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) - { - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return true; - return false; - } - - if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple))) - return true; - - if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple))) - { - /* it must have aborted or crashed */ - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - return true; - } - - /* xmax transaction committed */ - - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - { - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - return true; - } - - SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, - HeapTupleHeaderGetRawXmax(tuple)); - return false; -} - -/* - * HeapTupleSatisfiesAny - * Dummy "satisfies" routine: any tuple satisfies SnapshotAny. - */ -static bool -HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer) -{ - return true; -} - -/* - * HeapTupleSatisfiesToast - * True iff heap tuple is valid as a TOAST row. - * - * See SNAPSHOT_TOAST's definition for the intended behaviour. - * - * This is a simplified version that only checks for VACUUM moving conditions. - * It's appropriate for TOAST usage because TOAST really doesn't want to do - * its own time qual checks; if you can see the main table row that contains - * a TOAST reference, you should be able to see the TOASTed value. However, - * vacuuming a TOAST table is independent of the main table, and in case such - * a vacuum fails partway through, we'd better do this much checking. - * - * Among other things, this means you can't do UPDATEs of rows in a TOAST - * table. - */ -static bool -HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot, - Buffer buffer) -{ - HeapTupleHeader tuple = htup->t_data; - - Assert(ItemPointerIsValid(&htup->t_self)); - Assert(htup->t_tableOid != InvalidOid); - - if (!HeapTupleHeaderXminCommitted(tuple)) - { - if (HeapTupleHeaderXminInvalid(tuple)) - return false; - - /* Used by pre-9.0 binary upgrades */ - if (tuple->t_infomask & HEAP_MOVED_OFF) - { - TransactionId xvac = HeapTupleHeaderGetXvac(tuple); - - if (TransactionIdIsCurrentTransactionId(xvac)) - return false; - if (!TransactionIdIsInProgress(xvac)) - { - if (TransactionIdDidCommit(xvac)) - { - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return false; - } - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - InvalidTransactionId); - } - } - /* Used by pre-9.0 binary upgrades */ - else if (tuple->t_infomask & HEAP_MOVED_IN) - { - TransactionId xvac = HeapTupleHeaderGetXvac(tuple); - - if (!TransactionIdIsCurrentTransactionId(xvac)) - { - if (TransactionIdIsInProgress(xvac)) - return false; - if (TransactionIdDidCommit(xvac)) - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - InvalidTransactionId); - else - { - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return false; - } - } - } - - /* - * An invalid Xmin can be left behind by a speculative insertion that - * is canceled by super-deleting the tuple. This also applies to - * TOAST tuples created during speculative insertion. - */ - else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple))) - return false; - } - - /* otherwise assume the tuple is valid for TOAST. */ - return true; -} - -/* - * HeapTupleSatisfiesUpdate - * - * This function returns a more detailed result code than most of the - * functions in this file, since UPDATE needs to know more than "is it - * visible?". It also allows for user-supplied CommandId rather than - * relying on CurrentCommandId. - * - * The possible return codes are: - * - * HeapTupleInvisible: the tuple didn't exist at all when the scan started, - * e.g. it was created by a later CommandId. - * - * HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be - * updated. - * - * HeapTupleSelfUpdated: The tuple was updated by the current transaction, - * after the current scan started. - * - * HeapTupleUpdated: The tuple was updated by a committed transaction. - * - * HeapTupleBeingUpdated: The tuple is being updated by an in-progress - * transaction other than the current transaction. (Note: this includes - * the case where the tuple is share-locked by a MultiXact, even if the - * MultiXact includes the current transaction. Callers that want to - * distinguish that case must test for it themselves.) - */ -HTSU_Result -HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, - Buffer buffer) -{ - HeapTupleHeader tuple = htup->t_data; - - Assert(ItemPointerIsValid(&htup->t_self)); - Assert(htup->t_tableOid != InvalidOid); - - if (!HeapTupleHeaderXminCommitted(tuple)) - { - if (HeapTupleHeaderXminInvalid(tuple)) - return HeapTupleInvisible; - - /* Used by pre-9.0 binary upgrades */ - if (tuple->t_infomask & HEAP_MOVED_OFF) - { - TransactionId xvac = HeapTupleHeaderGetXvac(tuple); - - if (TransactionIdIsCurrentTransactionId(xvac)) - return HeapTupleInvisible; - if (!TransactionIdIsInProgress(xvac)) - { - if (TransactionIdDidCommit(xvac)) - { - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return HeapTupleInvisible; - } - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - InvalidTransactionId); - } - } - /* Used by pre-9.0 binary upgrades */ - else if (tuple->t_infomask & HEAP_MOVED_IN) - { - TransactionId xvac = HeapTupleHeaderGetXvac(tuple); - - if (!TransactionIdIsCurrentTransactionId(xvac)) - { - if (TransactionIdIsInProgress(xvac)) - return HeapTupleInvisible; - if (TransactionIdDidCommit(xvac)) - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - InvalidTransactionId); - else - { - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return HeapTupleInvisible; - } - } - } - else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple))) - { - if (HeapTupleHeaderGetCmin(tuple) >= curcid) - return HeapTupleInvisible; /* inserted after scan started */ - - if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */ - return HeapTupleMayBeUpdated; - - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - { - TransactionId xmax; - - xmax = HeapTupleHeaderGetRawXmax(tuple); - - /* - * Careful here: even though this tuple was created by our own - * transaction, it might be locked by other transactions, if - * the original version was key-share locked when we updated - * it. - */ - - if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) - { - if (MultiXactIdIsRunning(xmax, true)) - return HeapTupleBeingUpdated; - else - return HeapTupleMayBeUpdated; - } - - /* - * If the locker is gone, then there is nothing of interest - * left in this Xmax; otherwise, report the tuple as - * locked/updated. - */ - if (!TransactionIdIsInProgress(xmax)) - return HeapTupleMayBeUpdated; - return HeapTupleBeingUpdated; - } - - if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) - { - TransactionId xmax; - - xmax = HeapTupleGetUpdateXid(tuple); - - /* not LOCKED_ONLY, so it has to have an xmax */ - Assert(TransactionIdIsValid(xmax)); - - /* deleting subtransaction must have aborted */ - if (!TransactionIdIsCurrentTransactionId(xmax)) - { - if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), - false)) - return HeapTupleBeingUpdated; - return HeapTupleMayBeUpdated; - } - else - { - if (HeapTupleHeaderGetCmax(tuple) >= curcid) - return HeapTupleSelfUpdated; /* updated after scan - * started */ - else - return HeapTupleInvisible; /* updated before scan - * started */ - } - } - - if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) - { - /* deleting subtransaction must have aborted */ - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - return HeapTupleMayBeUpdated; - } - - if (HeapTupleHeaderGetCmax(tuple) >= curcid) - return HeapTupleSelfUpdated; /* updated after scan started */ - else - return HeapTupleInvisible; /* updated before scan started */ - } - else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple))) - return HeapTupleInvisible; - else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple))) - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - HeapTupleHeaderGetRawXmin(tuple)); - else - { - /* it must have aborted or crashed */ - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return HeapTupleInvisible; - } - } - - /* by here, the inserting transaction has committed */ - - if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */ - return HeapTupleMayBeUpdated; - - if (tuple->t_infomask & HEAP_XMAX_COMMITTED) - { - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return HeapTupleMayBeUpdated; - return HeapTupleUpdated; /* updated by other */ - } - - if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) - { - TransactionId xmax; - - if (HEAP_LOCKED_UPGRADED(tuple->t_infomask)) - return HeapTupleMayBeUpdated; - - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - { - if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true)) - return HeapTupleBeingUpdated; - - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); - return HeapTupleMayBeUpdated; - } - - xmax = HeapTupleGetUpdateXid(tuple); - if (!TransactionIdIsValid(xmax)) - { - if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false)) - return HeapTupleBeingUpdated; - } - - /* not LOCKED_ONLY, so it has to have an xmax */ - Assert(TransactionIdIsValid(xmax)); - - if (TransactionIdIsCurrentTransactionId(xmax)) - { - if (HeapTupleHeaderGetCmax(tuple) >= curcid) - return HeapTupleSelfUpdated; /* updated after scan started */ - else - return HeapTupleInvisible; /* updated before scan started */ - } - - if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false)) - return HeapTupleBeingUpdated; - - if (TransactionIdDidCommit(xmax)) - return HeapTupleUpdated; - - /* - * By here, the update in the Xmax is either aborted or crashed, but - * what about the other members? - */ - - if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false)) - { - /* - * There's no member, even just a locker, alive anymore, so we can - * mark the Xmax as invalid. - */ - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - return HeapTupleMayBeUpdated; - } - else - { - /* There are lockers running */ - return HeapTupleBeingUpdated; - } - } - - if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) - { - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return HeapTupleBeingUpdated; - if (HeapTupleHeaderGetCmax(tuple) >= curcid) - return HeapTupleSelfUpdated; /* updated after scan started */ - else - return HeapTupleInvisible; /* updated before scan started */ - } - - if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple))) - return HeapTupleBeingUpdated; - - if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple))) - { - /* it must have aborted or crashed */ - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - return HeapTupleMayBeUpdated; - } - - /* xmax transaction committed */ - - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - { - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - return HeapTupleMayBeUpdated; - } - - SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, - HeapTupleHeaderGetRawXmax(tuple)); - return HeapTupleUpdated; /* updated by other */ -} - -/* - * HeapTupleSatisfiesDirty - * True iff heap tuple is valid including effects of open transactions. - * - * See SNAPSHOT_DIRTY's definition for the intended behaviour. - * - * This is essentially like HeapTupleSatisfiesSelf as far as effects of - * the current transaction and committed/aborted xacts are concerned. - * However, we also include the effects of other xacts still in progress. - * - * A special hack is that the passed-in snapshot struct is used as an - * output argument to return the xids of concurrent xacts that affected the - * tuple. snapshot->xmin is set to the tuple's xmin if that is another - * transaction that's still in progress; or to InvalidTransactionId if the - * tuple's xmin is committed good, committed dead, or my own xact. - * Similarly for snapshot->xmax and the tuple's xmax. If the tuple was - * inserted speculatively, meaning that the inserter might still back down - * on the insertion without aborting the whole transaction, the associated - * token is also returned in snapshot->speculativeToken. - */ -static bool -HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot, - Buffer buffer) -{ - HeapTupleHeader tuple = htup->t_data; - - Assert(ItemPointerIsValid(&htup->t_self)); - Assert(htup->t_tableOid != InvalidOid); - - snapshot->xmin = snapshot->xmax = InvalidTransactionId; - snapshot->speculativeToken = 0; - - if (!HeapTupleHeaderXminCommitted(tuple)) - { - if (HeapTupleHeaderXminInvalid(tuple)) - return false; - - /* Used by pre-9.0 binary upgrades */ - if (tuple->t_infomask & HEAP_MOVED_OFF) - { - TransactionId xvac = HeapTupleHeaderGetXvac(tuple); - - if (TransactionIdIsCurrentTransactionId(xvac)) - return false; - if (!TransactionIdIsInProgress(xvac)) - { - if (TransactionIdDidCommit(xvac)) - { - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return false; - } - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - InvalidTransactionId); - } - } - /* Used by pre-9.0 binary upgrades */ - else if (tuple->t_infomask & HEAP_MOVED_IN) - { - TransactionId xvac = HeapTupleHeaderGetXvac(tuple); - - if (!TransactionIdIsCurrentTransactionId(xvac)) - { - if (TransactionIdIsInProgress(xvac)) - return false; - if (TransactionIdDidCommit(xvac)) - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - InvalidTransactionId); - else - { - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return false; - } - } - } - else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple))) - { - if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */ - return true; - - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */ - return true; - - if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) - { - TransactionId xmax; - - xmax = HeapTupleGetUpdateXid(tuple); - - /* not LOCKED_ONLY, so it has to have an xmax */ - Assert(TransactionIdIsValid(xmax)); - - /* updating subtransaction must have aborted */ - if (!TransactionIdIsCurrentTransactionId(xmax)) - return true; - else - return false; - } - - if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) - { - /* deleting subtransaction must have aborted */ - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - return true; - } - - return false; - } - else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple))) - { - /* - * Return the speculative token to caller. Caller can worry about - * xmax, since it requires a conclusively locked row version, and - * a concurrent update to this tuple is a conflict of its - * purposes. - */ - if (HeapTupleHeaderIsSpeculative(tuple)) - { - snapshot->speculativeToken = - HeapTupleHeaderGetSpeculativeToken(tuple); - - Assert(snapshot->speculativeToken != 0); - } - - snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple); - /* XXX shouldn't we fall through to look at xmax? */ - return true; /* in insertion by other */ - } - else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple))) - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - HeapTupleHeaderGetRawXmin(tuple)); - else - { - /* it must have aborted or crashed */ - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return false; - } - } - - /* by here, the inserting transaction has committed */ - - if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */ - return true; - - if (tuple->t_infomask & HEAP_XMAX_COMMITTED) - { - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return true; - return false; /* updated by other */ - } - - if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) - { - TransactionId xmax; - - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return true; - - xmax = HeapTupleGetUpdateXid(tuple); - - /* not LOCKED_ONLY, so it has to have an xmax */ - Assert(TransactionIdIsValid(xmax)); - - if (TransactionIdIsCurrentTransactionId(xmax)) - return false; - if (TransactionIdIsInProgress(xmax)) - { - snapshot->xmax = xmax; - return true; - } - if (TransactionIdDidCommit(xmax)) - return false; - /* it must have aborted or crashed */ - return true; - } - - if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) - { - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return true; - return false; - } - - if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple))) - { - if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple); - return true; - } - - if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple))) - { - /* it must have aborted or crashed */ - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - return true; - } - - /* xmax transaction committed */ - - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - { - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - return true; - } - - SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, - HeapTupleHeaderGetRawXmax(tuple)); - return false; /* updated by other */ -} - -/* - * HeapTupleSatisfiesMVCC - * True iff heap tuple is valid for the given MVCC snapshot. - * - * See SNAPSHOT_MVCC's definition for the intended behaviour. - * - * Notice that here, we will not update the tuple status hint bits if the - * inserting/deleting transaction is still running according to our snapshot, - * even if in reality it's committed or aborted by now. This is intentional. - * Checking the true transaction state would require access to high-traffic - * shared data structures, creating contention we'd rather do without, and it - * would not change the result of our visibility check anyway. The hint bits - * will be updated by the first visitor that has a snapshot new enough to see - * the inserting/deleting transaction as done. In the meantime, the cost of - * leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC - * call will need to run TransactionIdIsCurrentTransactionId in addition to - * XidInMVCCSnapshot (but it would have to do the latter anyway). In the old - * coding where we tried to set the hint bits as soon as possible, we instead - * did TransactionIdIsInProgress in each call --- to no avail, as long as the - * inserting/deleting transaction was still running --- which was more cycles - * and more contention on the PGXACT array. - */ -static bool -HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot, - Buffer buffer) -{ - HeapTupleHeader tuple = htup->t_data; - - Assert(ItemPointerIsValid(&htup->t_self)); - Assert(htup->t_tableOid != InvalidOid); - - if (!HeapTupleHeaderXminCommitted(tuple)) - { - if (HeapTupleHeaderXminInvalid(tuple)) - return false; - - /* Used by pre-9.0 binary upgrades */ - if (tuple->t_infomask & HEAP_MOVED_OFF) - { - TransactionId xvac = HeapTupleHeaderGetXvac(tuple); - - if (TransactionIdIsCurrentTransactionId(xvac)) - return false; - if (!XidInMVCCSnapshot(xvac, snapshot)) - { - if (TransactionIdDidCommit(xvac)) - { - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return false; - } - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - InvalidTransactionId); - } - } - /* Used by pre-9.0 binary upgrades */ - else if (tuple->t_infomask & HEAP_MOVED_IN) - { - TransactionId xvac = HeapTupleHeaderGetXvac(tuple); - - if (!TransactionIdIsCurrentTransactionId(xvac)) - { - if (XidInMVCCSnapshot(xvac, snapshot)) - return false; - if (TransactionIdDidCommit(xvac)) - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - InvalidTransactionId); - else - { - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return false; - } - } - } - else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple))) - { - if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid) - return false; /* inserted after scan started */ - - if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */ - return true; - - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */ - return true; - - if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) - { - TransactionId xmax; - - xmax = HeapTupleGetUpdateXid(tuple); - - /* not LOCKED_ONLY, so it has to have an xmax */ - Assert(TransactionIdIsValid(xmax)); - - /* updating subtransaction must have aborted */ - if (!TransactionIdIsCurrentTransactionId(xmax)) - return true; - else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid) - return true; /* updated after scan started */ - else - return false; /* updated before scan started */ - } - - if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) - { - /* deleting subtransaction must have aborted */ - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - return true; - } - - if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid) - return true; /* deleted after scan started */ - else - return false; /* deleted before scan started */ - } - else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot)) - return false; - else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple))) - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - HeapTupleHeaderGetRawXmin(tuple)); - else - { - /* it must have aborted or crashed */ - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return false; - } - } - else - { - /* xmin is committed, but maybe not according to our snapshot */ - if (!HeapTupleHeaderXminFrozen(tuple) && - XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot)) - return false; /* treat as still in progress */ - } - - /* by here, the inserting transaction has committed */ - - if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */ - return true; - - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return true; - - if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) - { - TransactionId xmax; - - /* already checked above */ - Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)); - - xmax = HeapTupleGetUpdateXid(tuple); - - /* not LOCKED_ONLY, so it has to have an xmax */ - Assert(TransactionIdIsValid(xmax)); - - if (TransactionIdIsCurrentTransactionId(xmax)) - { - if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid) - return true; /* deleted after scan started */ - else - return false; /* deleted before scan started */ - } - if (XidInMVCCSnapshot(xmax, snapshot)) - return true; - if (TransactionIdDidCommit(xmax)) - return false; /* updating transaction committed */ - /* it must have aborted or crashed */ - return true; - } - - if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) - { - if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) - { - if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid) - return true; /* deleted after scan started */ - else - return false; /* deleted before scan started */ - } - - if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot)) - return true; - - if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple))) - { - /* it must have aborted or crashed */ - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - return true; - } - - /* xmax transaction committed */ - SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, - HeapTupleHeaderGetRawXmax(tuple)); - } - else - { - /* xmax is committed, but maybe not according to our snapshot */ - if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot)) - return true; /* treat as still in progress */ - } - - /* xmax transaction committed */ - - return false; -} - - -/* - * HeapTupleSatisfiesVacuum - * - * Determine the status of tuples for VACUUM purposes. Here, what - * we mainly want to know is if a tuple is potentially visible to *any* - * running transaction. If so, it can't be removed yet by VACUUM. - * - * OldestXmin is a cutoff XID (obtained from GetOldestXmin()). Tuples - * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might - * still be visible to some open transaction, so we can't remove them, - * even if we see that the deleting transaction has committed. - */ -HTSV_Result -HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin, - Buffer buffer) -{ - HeapTupleHeader tuple = htup->t_data; - - Assert(ItemPointerIsValid(&htup->t_self)); - Assert(htup->t_tableOid != InvalidOid); - - /* - * Has inserting transaction committed? - * - * If the inserting transaction aborted, then the tuple was never visible - * to any other transaction, so we can delete it immediately. - */ - if (!HeapTupleHeaderXminCommitted(tuple)) - { - if (HeapTupleHeaderXminInvalid(tuple)) - return HEAPTUPLE_DEAD; - /* Used by pre-9.0 binary upgrades */ - else if (tuple->t_infomask & HEAP_MOVED_OFF) - { - TransactionId xvac = HeapTupleHeaderGetXvac(tuple); - - if (TransactionIdIsCurrentTransactionId(xvac)) - return HEAPTUPLE_DELETE_IN_PROGRESS; - if (TransactionIdIsInProgress(xvac)) - return HEAPTUPLE_DELETE_IN_PROGRESS; - if (TransactionIdDidCommit(xvac)) - { - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return HEAPTUPLE_DEAD; - } - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - InvalidTransactionId); - } - /* Used by pre-9.0 binary upgrades */ - else if (tuple->t_infomask & HEAP_MOVED_IN) - { - TransactionId xvac = HeapTupleHeaderGetXvac(tuple); - - if (TransactionIdIsCurrentTransactionId(xvac)) - return HEAPTUPLE_INSERT_IN_PROGRESS; - if (TransactionIdIsInProgress(xvac)) - return HEAPTUPLE_INSERT_IN_PROGRESS; - if (TransactionIdDidCommit(xvac)) - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - InvalidTransactionId); - else - { - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return HEAPTUPLE_DEAD; - } - } - else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple))) - { - if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */ - return HEAPTUPLE_INSERT_IN_PROGRESS; - /* only locked? run infomask-only check first, for performance */ - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) || - HeapTupleHeaderIsOnlyLocked(tuple)) - return HEAPTUPLE_INSERT_IN_PROGRESS; - /* inserted and then deleted by same xact */ - if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple))) - return HEAPTUPLE_DELETE_IN_PROGRESS; - /* deleting subtransaction must have aborted */ - return HEAPTUPLE_INSERT_IN_PROGRESS; - } - else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple))) - { - /* - * It'd be possible to discern between INSERT/DELETE in progress - * here by looking at xmax - but that doesn't seem beneficial for - * the majority of callers and even detrimental for some. We'd - * rather have callers look at/wait for xmin than xmax. It's - * always correct to return INSERT_IN_PROGRESS because that's - * what's happening from the view of other backends. - */ - return HEAPTUPLE_INSERT_IN_PROGRESS; - } - else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple))) - SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, - HeapTupleHeaderGetRawXmin(tuple)); - else - { - /* - * Not in Progress, Not Committed, so either Aborted or crashed - */ - SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, - InvalidTransactionId); - return HEAPTUPLE_DEAD; - } - - /* - * At this point the xmin is known committed, but we might not have - * been able to set the hint bit yet; so we can no longer Assert that - * it's set. - */ - } - - /* - * Okay, the inserter committed, so it was good at some point. Now what - * about the deleting transaction? - */ - if (tuple->t_infomask & HEAP_XMAX_INVALID) - return HEAPTUPLE_LIVE; - - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - { - /* - * "Deleting" xact really only locked it, so the tuple is live in any - * case. However, we should make sure that either XMAX_COMMITTED or - * XMAX_INVALID gets set once the xact is gone, to reduce the costs of - * examining the tuple for future xacts. - */ - if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) - { - if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) - { - /* - * If it's a pre-pg_upgrade tuple, the multixact cannot - * possibly be running; otherwise have to check. - */ - if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) && - MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), - true)) - return HEAPTUPLE_LIVE; - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); - } - else - { - if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple))) - return HEAPTUPLE_LIVE; - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - } - } - - /* - * We don't really care whether xmax did commit, abort or crash. We - * know that xmax did lock the tuple, but it did not and will never - * actually update it. - */ - - return HEAPTUPLE_LIVE; - } - - if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) - { - TransactionId xmax = HeapTupleGetUpdateXid(tuple); - - /* already checked above */ - Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)); - - /* not LOCKED_ONLY, so it has to have an xmax */ - Assert(TransactionIdIsValid(xmax)); - - if (TransactionIdIsInProgress(xmax)) - return HEAPTUPLE_DELETE_IN_PROGRESS; - else if (TransactionIdDidCommit(xmax)) - { - /* - * The multixact might still be running due to lockers. If the - * updater is below the xid horizon, we have to return DEAD - * regardless -- otherwise we could end up with a tuple where the - * updater has to be removed due to the horizon, but is not pruned - * away. It's not a problem to prune that tuple, because any - * remaining lockers will also be present in newer tuple versions. - */ - if (!TransactionIdPrecedes(xmax, OldestXmin)) - return HEAPTUPLE_RECENTLY_DEAD; - - return HEAPTUPLE_DEAD; - } - else if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false)) - { - /* - * Not in Progress, Not Committed, so either Aborted or crashed. - * Mark the Xmax as invalid. - */ - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); - } - - return HEAPTUPLE_LIVE; - } - - if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) - { - if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple))) - return HEAPTUPLE_DELETE_IN_PROGRESS; - else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple))) - SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, - HeapTupleHeaderGetRawXmax(tuple)); - else - { - /* - * Not in Progress, Not Committed, so either Aborted or crashed - */ - SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, - InvalidTransactionId); - return HEAPTUPLE_LIVE; - } - - /* - * At this point the xmax is known committed, but we might not have - * been able to set the hint bit yet; so we can no longer Assert that - * it's set. - */ - } - - /* - * Deleter committed, but perhaps it was recent enough that some open - * transactions could still see the tuple. - */ - if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin)) - return HEAPTUPLE_RECENTLY_DEAD; - - /* Otherwise, it's dead and removable */ - return HEAPTUPLE_DEAD; -} - - -/* - * HeapTupleSatisfiesNonVacuumable - * - * True if tuple might be visible to some transaction; false if it's - * surely dead to everyone, ie, vacuumable. - * - * See SNAPSHOT_TOAST's definition for the intended behaviour. - * - * This is an interface to HeapTupleSatisfiesVacuum that's callable via - * HeapTupleSatisfiesSnapshot, so it can be used through a Snapshot. - * snapshot->xmin must have been set up with the xmin horizon to use. - */ -static bool -HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot, - Buffer buffer) -{ - return HeapTupleSatisfiesVacuum(htup, snapshot->xmin, buffer) - != HEAPTUPLE_DEAD; -} - - -/* - * HeapTupleIsSurelyDead - * - * Cheaply determine whether a tuple is surely dead to all onlookers. - * We sometimes use this in lieu of HeapTupleSatisfiesVacuum when the - * tuple has just been tested by another visibility routine (usually - * HeapTupleSatisfiesMVCC) and, therefore, any hint bits that can be set - * should already be set. We assume that if no hint bits are set, the xmin - * or xmax transaction is still running. This is therefore faster than - * HeapTupleSatisfiesVacuum, because we don't consult PGXACT nor CLOG. - * It's okay to return false when in doubt, but we must return true only - * if the tuple is removable. - */ -bool -HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin) -{ - HeapTupleHeader tuple = htup->t_data; - - Assert(ItemPointerIsValid(&htup->t_self)); - Assert(htup->t_tableOid != InvalidOid); - - /* - * If the inserting transaction is marked invalid, then it aborted, and - * the tuple is definitely dead. If it's marked neither committed nor - * invalid, then we assume it's still alive (since the presumption is that - * all relevant hint bits were just set moments ago). - */ - if (!HeapTupleHeaderXminCommitted(tuple)) - return HeapTupleHeaderXminInvalid(tuple) ? true : false; - - /* - * If the inserting transaction committed, but any deleting transaction - * aborted, the tuple is still alive. - */ - if (tuple->t_infomask & HEAP_XMAX_INVALID) - return false; - - /* - * If the XMAX is just a lock, the tuple is still alive. - */ - if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return false; - - /* - * If the Xmax is a MultiXact, it might be dead or alive, but we cannot - * know without checking pg_multixact. - */ - if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) - return false; - - /* If deleter isn't known to have committed, assume it's still running. */ - if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) - return false; - - /* Deleter committed, so tuple is dead if the XID is old enough. */ - return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin); -} - -/* - * Is the tuple really only locked? That is, is it not updated? - * - * It's easy to check just infomask bits if the locker is not a multi; but - * otherwise we need to verify that the updating transaction has not aborted. - * - * This function is here because it follows the same time qualification rules - * laid out at the top of this file. - */ -bool -HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple) -{ - TransactionId xmax; - - /* if there's no valid Xmax, then there's obviously no update either */ - if (tuple->t_infomask & HEAP_XMAX_INVALID) - return true; - - if (tuple->t_infomask & HEAP_XMAX_LOCK_ONLY) - return true; - - /* invalid xmax means no update */ - if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple))) - return true; - - /* - * if HEAP_XMAX_LOCK_ONLY is not set and not a multi, then this must - * necessarily have been updated - */ - if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI)) - return false; - - /* ... but if it's a multi, then perhaps the updating Xid aborted. */ - xmax = HeapTupleGetUpdateXid(tuple); - - /* not LOCKED_ONLY, so it has to have an xmax */ - Assert(TransactionIdIsValid(xmax)); - - if (TransactionIdIsCurrentTransactionId(xmax)) - return false; - if (TransactionIdIsInProgress(xmax)) - return false; - if (TransactionIdDidCommit(xmax)) - return false; - - /* - * not current, not in progress, not committed -- must have aborted or - * crashed - */ - return true; -} - -/* - * check whether the transaction id 'xid' is in the pre-sorted array 'xip'. - */ -static bool -TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num) -{ - return bsearch(&xid, xip, num, - sizeof(TransactionId), xidComparator) != NULL; -} - -/* - * See the comments for HeapTupleSatisfiesMVCC for the semantics this function - * obeys. - * - * Only usable on tuples from catalog tables! - * - * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support - * reading catalog pages which couldn't have been created in an older version. - * - * We don't set any hint bits in here as it seems unlikely to be beneficial as - * those should already be set by normal access and it seems to be too - * dangerous to do so as the semantics of doing so during timetravel are more - * complicated than when dealing "only" with the present. - */ -static bool -HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot, - Buffer buffer) -{ - HeapTupleHeader tuple = htup->t_data; - TransactionId xmin = HeapTupleHeaderGetXmin(tuple); - TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple); - - Assert(ItemPointerIsValid(&htup->t_self)); - Assert(htup->t_tableOid != InvalidOid); - - /* inserting transaction aborted */ - if (HeapTupleHeaderXminInvalid(tuple)) - { - Assert(!TransactionIdDidCommit(xmin)); - return false; - } - /* check if it's one of our txids, toplevel is also in there */ - else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt)) - { - bool resolved; - CommandId cmin = HeapTupleHeaderGetRawCommandId(tuple); - CommandId cmax = InvalidCommandId; - - /* - * another transaction might have (tried to) delete this tuple or - * cmin/cmax was stored in a combocid. So we need to lookup the actual - * values externally. - */ - resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot, - htup, buffer, - &cmin, &cmax); - - if (!resolved) - elog(ERROR, "could not resolve cmin/cmax of catalog tuple"); - - Assert(cmin != InvalidCommandId); - - if (cmin >= snapshot->curcid) - return false; /* inserted after scan started */ - /* fall through */ - } - /* committed before our xmin horizon. Do a normal visibility check. */ - else if (TransactionIdPrecedes(xmin, snapshot->xmin)) - { - Assert(!(HeapTupleHeaderXminCommitted(tuple) && - !TransactionIdDidCommit(xmin))); - - /* check for hint bit first, consult clog afterwards */ - if (!HeapTupleHeaderXminCommitted(tuple) && - !TransactionIdDidCommit(xmin)) - return false; - /* fall through */ - } - /* beyond our xmax horizon, i.e. invisible */ - else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax)) - { - return false; - } - /* check if it's a committed transaction in [xmin, xmax) */ - else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt)) - { - /* fall through */ - } - - /* - * none of the above, i.e. between [xmin, xmax) but hasn't committed. I.e. - * invisible. - */ - else - { - return false; - } - - /* at this point we know xmin is visible, go on to check xmax */ - - /* xid invalid or aborted */ - if (tuple->t_infomask & HEAP_XMAX_INVALID) - return true; - /* locked tuples are always visible */ - else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return true; - - /* - * We can see multis here if we're looking at user tables or if somebody - * SELECT ... FOR SHARE/UPDATE a system table. - */ - else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) - { - xmax = HeapTupleGetUpdateXid(tuple); - } - - /* check if it's one of our txids, toplevel is also in there */ - if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt)) - { - bool resolved; - CommandId cmin; - CommandId cmax = HeapTupleHeaderGetRawCommandId(tuple); - - /* Lookup actual cmin/cmax values */ - resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot, - htup, buffer, - &cmin, &cmax); - - if (!resolved) - elog(ERROR, "could not resolve combocid to cmax"); - - Assert(cmax != InvalidCommandId); - - if (cmax >= snapshot->curcid) - return true; /* deleted after scan started */ - else - return false; /* deleted before scan started */ - } - /* below xmin horizon, normal transaction state is valid */ - else if (TransactionIdPrecedes(xmax, snapshot->xmin)) - { - Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED && - !TransactionIdDidCommit(xmax))); - - /* check hint bit first */ - if (tuple->t_infomask & HEAP_XMAX_COMMITTED) - return false; - - /* check clog */ - return !TransactionIdDidCommit(xmax); - } - /* above xmax horizon, we cannot possibly see the deleting transaction */ - else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax)) - return true; - /* xmax is between [xmin, xmax), check known committed array */ - else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt)) - return false; - /* xmax is between [xmin, xmax), but known not to have committed yet */ - else - return true; -} - -/* - * HeapTupleSatisfiesVisibility - * True iff heap tuple satisfies a time qual. - * - * Notes: - * Assumes heap tuple is valid, and buffer at least share locked. - * - * Hint bits in the HeapTuple's t_infomask may be updated as a side effect; - * if so, the indicated buffer is marked dirty. - */ -bool -HeapTupleSatisfiesVisibility(HeapTuple tup, Snapshot snapshot, Buffer buffer) -{ - switch (snapshot->snapshot_type) - { - case SNAPSHOT_MVCC: - return HeapTupleSatisfiesMVCC(tup, snapshot, buffer); - break; - case SNAPSHOT_SELF: - return HeapTupleSatisfiesSelf(tup, snapshot, buffer); - break; - case SNAPSHOT_ANY: - return HeapTupleSatisfiesAny(tup, snapshot, buffer); - break; - case SNAPSHOT_TOAST: - return HeapTupleSatisfiesToast(tup, snapshot, buffer); - break; - case SNAPSHOT_DIRTY: - return HeapTupleSatisfiesDirty(tup, snapshot, buffer); - break; - case SNAPSHOT_HISTORIC_MVCC: - return HeapTupleSatisfiesHistoricMVCC(tup, snapshot, buffer); - break; - case SNAPSHOT_NON_VACUUMABLE: - return HeapTupleSatisfiesNonVacuumable(tup, snapshot, buffer); - break; - } - - return false; /* keep compiler quiet */ -} diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 6a2489ede3c..ab0879138f0 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -60,6 +60,15 @@ typedef struct HeapUpdateFailureData CommandId cmax; } HeapUpdateFailureData; +/* Result codes for HeapTupleSatisfiesVacuum */ +typedef enum +{ + HEAPTUPLE_DEAD, /* tuple is dead and deletable */ + HEAPTUPLE_LIVE, /* tuple is live (committed, no deleter) */ + HEAPTUPLE_RECENTLY_DEAD, /* tuple is dead, but not deletable yet */ + HEAPTUPLE_INSERT_IN_PROGRESS, /* inserting xact is still in progress */ + HEAPTUPLE_DELETE_IN_PROGRESS /* deleting xact is still in progress */ +} HTSV_Result; /* ---------------- * function prototypes for heap access method @@ -178,4 +187,29 @@ extern Size SyncScanShmemSize(void); struct VacuumParams; extern void heap_vacuum_rel(Relation onerel, int options, struct VacuumParams *params, BufferAccessStrategy bstrategy); + +/* in heap/heapam_visibility.c */ +extern bool HeapTupleSatisfiesVisibility(HeapTuple stup, Snapshot snapshot, + Buffer buffer); +extern HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple stup, CommandId curcid, + Buffer buffer); +extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple stup, TransactionId OldestXmin, + Buffer buffer); +extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer, + uint16 infomask, TransactionId xid); +extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple); +extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot); +extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin); + +/* + * To avoid leaking too much knowledge about reorderbuffer implementation + * details this is implemented in reorderbuffer.c not heapam_visibility.c + */ +struct HTAB; +extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data, + Snapshot snapshot, + HeapTuple htup, + Buffer buffer, + CommandId *cmin, CommandId *cmax); + #endif /* HEAPAM_H */ diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h index 952307103fc..e7ea5cf7b56 100644 --- a/src/include/utils/snapshot.h +++ b/src/include/utils/snapshot.h @@ -185,8 +185,7 @@ typedef struct SnapshotData } SnapshotData; /* - * Result codes for HeapTupleSatisfiesUpdate. This should really be in - * tqual.h, but we want to avoid including that file elsewhere. + * Result codes for HeapTupleSatisfiesUpdate. */ typedef enum { diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h deleted file mode 100644 index de8c799ea5e..00000000000 --- a/src/include/utils/tqual.h +++ /dev/null @@ -1,56 +0,0 @@ -/*------------------------------------------------------------------------- - * - * tqual.h - * POSTGRES "time qualification" definitions, ie, tuple visibility rules. - * - * Should be moved/renamed... - vadim 07/28/98 - * - * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * src/include/utils/tqual.h - * - *------------------------------------------------------------------------- - */ -#ifndef TQUAL_H -#define TQUAL_H - -#include "utils/snapshot.h" - -extern bool HeapTupleSatisfiesVisibility(HeapTuple stup, Snapshot snapshot, - Buffer buffer); - -/* Result codes for HeapTupleSatisfiesVacuum */ -typedef enum -{ - HEAPTUPLE_DEAD, /* tuple is dead and deletable */ - HEAPTUPLE_LIVE, /* tuple is live (committed, no deleter) */ - HEAPTUPLE_RECENTLY_DEAD, /* tuple is dead, but not deletable yet */ - HEAPTUPLE_INSERT_IN_PROGRESS, /* inserting xact is still in progress */ - HEAPTUPLE_DELETE_IN_PROGRESS /* deleting xact is still in progress */ -} HTSV_Result; - -/* Special "satisfies" routines with different APIs */ -extern HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple htup, - CommandId curcid, Buffer buffer); -extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple htup, - TransactionId OldestXmin, Buffer buffer); -extern bool HeapTupleIsSurelyDead(HeapTuple htup, - TransactionId OldestXmin); - -extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer, - uint16 infomask, TransactionId xid); -extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple); - -/* - * To avoid leaking too much knowledge about reorderbuffer implementation - * details this is implemented in reorderbuffer.c not tqual.c. - */ -struct HTAB; -extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data, - Snapshot snapshot, - HeapTuple htup, - Buffer buffer, - CommandId *cmin, CommandId *cmax); - -#endif /* TQUAL_H */ -- cgit v1.2.3