blob: 623dbdd405afa336bec636a2c05da79a258890cf [file] [log] [blame]
/*-------------------------------------------------------------------------
*
* 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, SetBufferCommitInfoNeedsSave 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: must check TransactionIdIsInProgress (which looks in PGPROC array)
* before TransactionIdDidCommit/TransactionIdDidAbort (which look in
* pg_clog). 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_clog before it unsets
* MyProc->xid in PGPROC 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.
*
* Summary of visibility functions:
*
* HeapTupleSatisfiesMVCC()
* visible to supplied snapshot, excludes current command
* HeapTupleSatisfiesNow()
* visible to instant snapshot, excludes current command
* HeapTupleSatisfiesUpdate()
* like HeapTupleSatisfiesNow(), but 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
* HeapTupleSatisfiesToast()
* visible unless part of interrupted vacuum, used for TOAST
* HeapTupleSatisfiesAny()
* all tuples are visible
*
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/time/tqual.c,v 1.100 2006/11/17 18:00:15 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/multixact.h"
#include "access/subtrans.h"
#include "access/transam.h"
#include "access/xact.h"
#include "storage/bufmgr.h"
#include "utils/tqual.h"
#include "access/twophase.h" /*max_prepared_xacts*/
#include "miscadmin.h"
#include "lib/stringinfo.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "storage/procarray.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "utils/builtins.h"
#include "cdb/cdbvars.h"
#include "access/clog.h"
#include "storage/buffile.h"
#include "postmaster/primary_mirror_mode.h"
/*
* These SnapshotData structs are static to simplify memory allocation
* (see the hack in GetSnapshotData to avoid repeated malloc/free).
*/
static SnapshotData SnapshotDirtyData;
static SnapshotData SerializableSnapshotData;
static SnapshotData LatestSnapshotData;
/* Externally visible pointers to valid snapshots: */
Snapshot SnapshotDirty = &SnapshotDirtyData;
Snapshot SerializableSnapshot = NULL;
Snapshot LatestSnapshot = NULL;
/*
* This pointer is not maintained by this module, but it's convenient
* to declare it here anyway. Callers typically assign a copy of
* GetTransactionSnapshot's result to ActiveSnapshot.
*/
Snapshot ActiveSnapshot = NULL;
/* These are updated by GetSnapshotData: */
TransactionId TransactionXmin = InvalidTransactionId;
TransactionId RecentXmin = InvalidTransactionId;
TransactionId RecentGlobalXmin = InvalidTransactionId;
#ifdef WATCH_VISIBILITY_IN_ACTION
uint8 WatchVisibilityFlags[WATCH_VISIBILITY_BYTE_LEN];
#endif
/* local functions */
static bool
XidInSnapshot(TransactionId xid, Snapshot snapshot, bool isXmax,
bool distributedSnapshotIgnore, bool *setDistributedSnapshotIgnore);
static bool
XidInSnapshot_Local(TransactionId xid, Snapshot snapshot, bool isXmax);
/* MPP Shared Snapshot. */
typedef struct SharedSnapshotStruct
{
int numSlots; /* number of valid Snapshot entries */
int maxSlots; /* allocated size of sharedSnapshotArray */
int nextSlot; /* points to the next avail slot. */
/*
* We now allow direct indexing into this array.
*
* We allocate the XIPS below.
*
* Be very careful when accessing fields inside here.
*/
SharedSnapshotSlot *slots;
TransactionId *xips; /* VARIABLE LENGTH ARRAY */
} SharedSnapshotStruct;
static volatile SharedSnapshotStruct *sharedSnapshotArray;
static Size slotSize = 0;
static Size slotCount = 0;
static Size xipEntryCount = 0;
/*
* Report shared-memory space needed by CreateSharedSnapshot.
*/
Size
SharedSnapshotShmemSize(void)
{
Size size;
xipEntryCount = MaxBackends + max_prepared_xacts;
slotSize = sizeof(SharedSnapshotSlot);
slotSize += mul_size(sizeof(TransactionId), (xipEntryCount));
slotSize = MAXALIGN(slotSize);
/*
* We only really need max_prepared_xacts; but for safety we
* multiply that by two (to account for slow de-allocation on
* cleanup, for instance).
*/
slotCount = 2 * max_prepared_xacts;
size = offsetof(SharedSnapshotStruct, xips);
size = add_size(size, mul_size(slotSize, slotCount));
return MAXALIGN(size);
}
/*
* Initialize the sharedSnapshot array. This array is used to communicate
* snapshots between qExecs that are segmates.
*/
void
CreateSharedSnapshotArray(void)
{
bool found;
int i;
TransactionId *xip_base=NULL;
/* Create or attach to the SharedSnapshot shared structure */
sharedSnapshotArray = (SharedSnapshotStruct *)
ShmemInitStruct("Shared Snapshot", SharedSnapshotShmemSize(), &found);
Assert(slotCount != 0);
Assert(xipEntryCount != 0);
if (!found)
{
/*
* We're the first - initialize.
*/
sharedSnapshotArray->numSlots = 0;
/* TODO: MaxBackends is only somewhat right. What we really want here
* is the MaxBackends value from the QD. But this is at least
* safe since we know we dont need *MORE* than MaxBackends. But
* in general MaxBackends on a QE is going to be bigger than on a
* QE by a good bit. or at least it should be.
*
* But really, max_prepared_transactions *is* what we want (it
* corresponds to the number of connections allowed on the
* master).
*
* slotCount is initialized in SharedSnapshotShmemSize().
*/
sharedSnapshotArray->maxSlots = slotCount;
sharedSnapshotArray->nextSlot = 0;
sharedSnapshotArray->slots = (SharedSnapshotSlot *)&sharedSnapshotArray->xips;
/* xips start just after the last slot structure */
xip_base = (TransactionId *)&sharedSnapshotArray->slots[sharedSnapshotArray->maxSlots];
for (i=0; i < sharedSnapshotArray->maxSlots; i++)
{
SharedSnapshotSlot *tmpSlot = &sharedSnapshotArray->slots[i];
tmpSlot->slotid = -1;
tmpSlot->contentid = UNINITIALIZED_GP_IDENTITY_VALUE;
tmpSlot->slotindex = i;
/*
* Fixup xip array pointer reference space allocated after slot structs:
*
* Note: xipEntryCount is initialized in SharedSnapshotShmemSize().
* So each slot gets (MaxBackends + max_prepared_xacts) transaction-ids.
*/
tmpSlot->snapshot.xip = &xip_base[xipEntryCount];
xip_base += xipEntryCount;
}
}
}
/*
* Used to dump the internal state of the shared slots for debugging.
*/
char *
SharedSnapshotDump(void)
{
StringInfoData str;
volatile SharedSnapshotStruct *arrayP = sharedSnapshotArray;
int index;
initStringInfo(&str);
appendStringInfo(&str, "Local SharedSnapshot Slot Dump: currSlots: %d maxSlots: %d ",
arrayP->numSlots, arrayP->maxSlots);
LWLockAcquire(SharedSnapshotLock, LW_EXCLUSIVE);
for (index=0; index < arrayP->maxSlots; index++)
{
/* need to do byte addressing to find the right slot */
SharedSnapshotSlot *testSlot = &arrayP->slots[index];
if (testSlot->slotid != -1)
{
appendStringInfo(&str, "(SLOT index: %d slotid: %d contentid: %d QDxid: %u QDcid: %u pid: %u)",
testSlot->slotindex, testSlot->slotid, testSlot->contentid, testSlot->QDxid, testSlot->QDcid, (int)testSlot->pid);
}
}
LWLockRelease(SharedSnapshotLock);
return str.data;
}
void
GetSlotTableDebugInfo(void **snapshotArray, int *maxSlots)
{
*snapshotArray = (void *)sharedSnapshotArray;
*maxSlots = sharedSnapshotArray->maxSlots;
}
/*
* Set the buffer dirty after setting t_infomask
*/
static inline
void markDirty(Buffer buffer, bool disabled, Relation relation, HeapTupleHeader tuple, bool isXmin)
{
TransactionId xid;
if (!disabled)
{
WATCH_VISIBILITY_ADDPAIR(
WATCH_VISIBILITY_GUC_OFF_MARK_BUFFFER_DIRTY_FOR_XMIN, !isXmin);
SetBufferCommitInfoNeedsSave(buffer);
return;
}
/*
* The GUC gp_disable_tuple_hints is on. Do further evaluation whether we want to write out the
* buffer or not.
*/
if (relation == NULL)
{
WATCH_VISIBILITY_ADDPAIR(
WATCH_VISIBILITY_NO_REL_MARK_BUFFFER_DIRTY_FOR_XMIN, !isXmin);
SetBufferCommitInfoNeedsSave(buffer);
return;
}
if (relation->rd_issyscat)
{
/* Assume we want to always mark the buffer dirty */
WATCH_VISIBILITY_ADDPAIR(
WATCH_VISIBILITY_SYS_CAT_MARK_BUFFFER_DIRTY_FOR_XMIN, !isXmin);
SetBufferCommitInfoNeedsSave(buffer);
return;
}
/*
* Get the xid whose hint bits were just set.
*/
if (isXmin)
xid = HeapTupleHeaderGetXmin(tuple);
else
xid = HeapTupleHeaderGetXmax(tuple);
if (xid == InvalidTransactionId)
{
WATCH_VISIBILITY_ADDPAIR(
WATCH_VISIBILITY_NO_XID_MARK_BUFFFER_DIRTY_FOR_XMIN, !isXmin);
SetBufferCommitInfoNeedsSave(buffer);
return;
}
/*
* Check age of the affected xid. If it is too old, mark the buffer to be written.
*/
if (CLOGTransactionIsOld(xid))
{
WATCH_VISIBILITY_ADDPAIR(
WATCH_VISIBILITY_TOO_OLD_MARK_BUFFFER_DIRTY_FOR_XMIN, !isXmin);
SetBufferCommitInfoNeedsSave(buffer);
return;
}
else
{
WATCH_VISIBILITY_ADDPAIR(
WATCH_VISIBILITY_YOUNG_DO_NOT_MARK_BUFFFER_DIRTY_FOR_XMIN, !isXmin);
}
}
#ifdef WATCH_VISIBILITY_IN_ACTION
static char WatchVisibilityXminCurrent[2000];
static char WatchVisibilityXmaxCurrent[2000];
#endif
/*
* HeapTupleSatisfiesItself
* True iff heap tuple is valid "for itself".
*
* Here, we consider the effects of:
* all committed transactions (as of the current instant)
* previous commands of this transaction
* changes made by the current command
*
* 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
*/
bool
HeapTupleSatisfiesItself(Relation relation, HeapTupleHeader tuple, Buffer buffer)
{
const bool disableTupleHints = gp_disable_tuple_hints;
WATCH_VISIBILITY_CLEAR();
if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED))
{
if (tuple->t_infomask & HEAP_XMIN_INVALID)
return false;
if (tuple->t_infomask & HEAP_MOVED_OFF)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
if (TransactionIdIsCurrentTransactionId(xvac))
return false;
if (!TransactionIdIsInProgress(xvac))
{
if (TransactionIdDidCommit(xvac))
{
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
return false;
}
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
}
}
else if (tuple->t_infomask & HEAP_MOVED_IN)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
if (!TransactionIdIsCurrentTransactionId(xvac))
{
if (TransactionIdIsInProgress(xvac))
return false;
if (TransactionIdDidCommit(xvac))
{
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
}
else
{
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
return false;
}
}
}
else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple)))
{
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
return true;
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
/* deleting subtransaction aborted? */
if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple)))
{
tuple->t_infomask |= HEAP_XMAX_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
return true;
}
Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)));
return false;
}
else if (TransactionIdIsInProgress(HeapTupleHeaderGetXmin(tuple)))
return false;
else if (TransactionIdDidCommit(HeapTupleHeaderGetXmin(tuple)))
{
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
}
else
{
/* it must have aborted or crashed */
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
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 (tuple->t_infomask & HEAP_IS_LOCKED)
return true;
return false; /* updated by other */
}
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
/* MultiXacts are currently only allowed to lock tuples */
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
return true;
}
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
{
if (tuple->t_infomask & HEAP_IS_LOCKED)
return true;
return false;
}
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(tuple)))
return true;
if (!TransactionIdDidCommit(HeapTupleHeaderGetXmax(tuple)))
{
/* it must have aborted or crashed */
tuple->t_infomask |= HEAP_XMAX_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
return true;
}
/* xmax transaction committed */
if (tuple->t_infomask & HEAP_IS_LOCKED)
{
tuple->t_infomask |= HEAP_XMAX_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
return true;
}
tuple->t_infomask |= HEAP_XMAX_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
return false;
}
/*
* HeapTupleSatisfiesNow
* True iff heap tuple is valid "now".
*
* Here, we consider the effects of:
* all committed transactions (as of the current instant)
* previous commands of this transaction
*
* Note we do _not_ include changes made by the current command. This
* solves the "Halloween problem" wherein an UPDATE might try to re-update
* its own output tuples.
*
* Note:
* Assumes heap tuple is valid.
*
* The satisfaction of "now" requires the following:
*
* ((Xmin == my-transaction && inserted by the current transaction
* Cmin < my-command && before this command, and
* (Xmax is null || the row has not been deleted, or
* (Xmax == my-transaction && it was deleted by the current transaction
* Cmax >= my-command))) but not before this command,
* || or
* (Xmin is committed && the row was inserted by a committed transaction, and
* (Xmax is null || the row has not been deleted, or
* (Xmax == my-transaction && the row is being deleted by this transaction
* Cmax >= my-command) || but it's not deleted "yet", or
* (Xmax != my-transaction && the row was deleted by another transaction
* Xmax is not committed)))) that has not been committed
*
* mao says 17 march 1993: the tests in this routine are correct;
* if you think they're not, you're wrong, and you should think
* about it again. i know, it happened to me. we don't need to
* check commit time against the start time of this transaction
* because 2ph locking protects us from doing the wrong thing.
* if you mess around here, you'll break serializability. the only
* problem with this code is that it does the wrong thing for system
* catalog updates, because the catalogs aren't subject to 2ph, so
* the serializability guarantees we provide don't extend to xacts
* that do catalog accesses. this is unfortunate, but not critical.
*/
bool
HeapTupleSatisfiesNow(Relation relation, HeapTupleHeader tuple, Buffer buffer)
{
const bool disableTupleHints = gp_disable_tuple_hints;
WATCH_VISIBILITY_CLEAR();
if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_NOT_HINT_COMMITTED);
if (tuple->t_infomask & HEAP_XMIN_INVALID)
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_ABORTED);
return false;
}
if (tuple->t_infomask & HEAP_MOVED_OFF)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_MOVED_AWAY_BY_VACUUM);
if (TransactionIdIsCurrentTransactionId(xvac))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_VACUUM_XID_CURRENT);
return false;
}
if (!TransactionIdIsInProgress(xvac))
{
if (TransactionIdDidCommit(xvac))
{
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMIN_VACUUM_MOVED_INVALID);
return false;
}
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMIN_COMMITTED);
}
}
else if (tuple->t_infomask & HEAP_MOVED_IN)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_MOVED_IN_BY_VACUUM);
if (!TransactionIdIsCurrentTransactionId(xvac))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_VACUUM_XID_NOT_CURRENT);
if (TransactionIdIsInProgress(xvac))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_VACUUM_XID_IN_PROGRESS);
return false;
}
if (TransactionIdDidCommit(xvac))
{
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMIN_COMMITTED);
}
else
{
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMIN_ABORTED);
return false;
}
}
}
else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple)))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_CURRENT);
#ifdef WATCH_VISIBILITY_IN_ACTION
strcpy(WatchVisibilityXminCurrent, WatchCurrentTransactionString());
#endif
if (HeapTupleHeaderGetCmin(tuple) >= GetCurrentCommandId())
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_INSERTED_AFTER_SCAN_STARTED);
return false; /* inserted after scan started */
}
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMAX_INVALID);
return true;
}
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_LOCKED);
return true;
}
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
/* deleting subtransaction aborted? */
if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple)))
{
tuple->t_infomask |= HEAP_XMAX_INVALID;
/* If ComboCID format cid, roll it back to normal Cmin */
if (tuple->t_infomask & HEAP_COMBOCID)
{
HeapTupleHeaderSetCmin(tuple, HeapTupleHeaderGetCmin(tuple));
}
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMAX_ABORTED);
return true;
}
Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)));
if (HeapTupleHeaderGetCmax(tuple) >= GetCurrentCommandId())
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_DELETED_AFTER_SCAN_STARTED);
return true; /* deleted after scan started */
}
else
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_DELETED_BEFORE_SCAN_STARTED);
return false; /* deleted before scan started */
}
}
else
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_NOT_CURRENT);
#ifdef WATCH_VISIBILITY_IN_ACTION
strcpy(WatchVisibilityXminCurrent, WatchCurrentTransactionString());
#endif
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmin(tuple)))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_IN_PROGRESS);
return false;
}
else if (TransactionIdDidCommit(HeapTupleHeaderGetXmin(tuple)))
{
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMIN_COMMITTED);
}
else
{
/* it must have aborted or crashed */
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMIN_ABORTED);
return false;
}
}
}
/* by here, the inserting transaction has committed */
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMAX_INVALID_OR_ABORTED);
return true;
}
if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMAX_HINT_COMMITTED);
if (tuple->t_infomask & HEAP_IS_LOCKED)
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_LOCKED);
return true;
}
return false;
}
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMAX_MULTIXACT);
/* MultiXacts are currently only allowed to lock tuples */
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
return true;
}
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMAX_CURRENT);
if (tuple->t_infomask & HEAP_IS_LOCKED)
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_LOCKED);
return true;
}
if (HeapTupleHeaderGetCmax(tuple) >= GetCurrentCommandId())
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_DELETED_AFTER_SCAN_STARTED);
return true; /* deleted after scan started */
}
else
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_DELETED_BEFORE_SCAN_STARTED);
return false; /* deleted before scan started */
}
}
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(tuple)))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMAX_IN_PROGRESS);
return true;
}
if (!TransactionIdDidCommit(HeapTupleHeaderGetXmax(tuple)))
{
/* it must have aborted or crashed */
tuple->t_infomask |= HEAP_XMAX_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMAX_ABORTED);
return true;
}
/* xmax transaction committed */
if (tuple->t_infomask & HEAP_IS_LOCKED)
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_LOCKED);
tuple->t_infomask |= HEAP_XMAX_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
return true;
}
tuple->t_infomask |= HEAP_XMAX_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMAX_COMMITTED);
return false;
}
/*
* HeapTupleSatisfiesToast
* True iff heap tuple is valid as a TOAST row.
*
* 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.
*/
bool
HeapTupleSatisfiesToast(Relation relation, HeapTupleHeader tuple, Buffer buffer)
{
const bool disableTupleHints = gp_disable_tuple_hints;
WATCH_VISIBILITY_CLEAR();
if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED))
{
if (tuple->t_infomask & HEAP_XMIN_INVALID)
return false;
if (tuple->t_infomask & HEAP_MOVED_OFF)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
if (TransactionIdIsCurrentTransactionId(xvac))
return false;
if (!TransactionIdIsInProgress(xvac))
{
if (TransactionIdDidCommit(xvac))
{
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
return false;
}
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
}
}
else if (tuple->t_infomask & HEAP_MOVED_IN)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
if (!TransactionIdIsCurrentTransactionId(xvac))
{
if (TransactionIdIsInProgress(xvac))
return false;
if (TransactionIdDidCommit(xvac))
{
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
}
else
{
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
return false;
}
}
}
}
/* otherwise assume the tuple is valid for TOAST. */
return true;
}
/*
* HeapTupleSatisfiesUpdate
*
* Same logic as HeapTupleSatisfiesNow, but returns a more detailed result
* code, since UPDATE needs to know more than "is it visible?". Also,
* tuples of my own xact are tested against the passed CommandId not
* 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(Relation relation, HeapTupleHeader tuple, CommandId curcid,
Buffer buffer)
{
const bool disableTupleHints = gp_disable_tuple_hints;
WATCH_VISIBILITY_CLEAR();
if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED))
{
if (tuple->t_infomask & HEAP_XMIN_INVALID)
return HeapTupleInvisible;
if (tuple->t_infomask & HEAP_MOVED_OFF)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
if (TransactionIdIsCurrentTransactionId(xvac))
return HeapTupleInvisible;
if (!TransactionIdIsInProgress(xvac))
{
if (TransactionIdDidCommit(xvac))
{
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
return HeapTupleInvisible;
}
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
}
}
else if (tuple->t_infomask & HEAP_MOVED_IN)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
if (!TransactionIdIsCurrentTransactionId(xvac))
{
if (TransactionIdIsInProgress(xvac))
return HeapTupleInvisible;
if (TransactionIdDidCommit(xvac))
{
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
}
else
{
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
return HeapTupleInvisible;
}
}
}
else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple)))
{
if (HeapTupleHeaderGetCmin(tuple) >= curcid)
return HeapTupleInvisible; /* inserted after scan started */
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return HeapTupleMayBeUpdated;
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
return HeapTupleMayBeUpdated;
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
/* deleting subtransaction aborted? */
if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple)))
{
tuple->t_infomask |= HEAP_XMAX_INVALID;
/* If ComboCID format cid, roll it back to normal Cmin */
if (tuple->t_infomask & HEAP_COMBOCID)
{
HeapTupleHeaderSetCmin(tuple, HeapTupleHeaderGetCmin(tuple));
}
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
return HeapTupleMayBeUpdated;
}
Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)));
if (HeapTupleHeaderGetCmax(tuple) >= curcid)
return HeapTupleSelfUpdated; /* updated after scan started */
else
return HeapTupleInvisible; /* updated before scan started */
}
else if (TransactionIdIsInProgress(HeapTupleHeaderGetXmin(tuple)))
return HeapTupleInvisible;
else if (TransactionIdDidCommit(HeapTupleHeaderGetXmin(tuple)))
{
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
}
else
{
/* it must have aborted or crashed */
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
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 (tuple->t_infomask & HEAP_IS_LOCKED)
return HeapTupleMayBeUpdated;
return HeapTupleUpdated; /* updated by other */
}
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
/* MultiXacts are currently only allowed to lock tuples */
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
if (MultiXactIdIsRunning(HeapTupleHeaderGetXmax(tuple)))
return HeapTupleBeingUpdated;
tuple->t_infomask |= HEAP_XMAX_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
return HeapTupleMayBeUpdated;
}
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
{
if (tuple->t_infomask & HEAP_IS_LOCKED)
return HeapTupleMayBeUpdated;
if (HeapTupleHeaderGetCmax(tuple) >= curcid)
return HeapTupleSelfUpdated; /* updated after scan started */
else
return HeapTupleInvisible; /* updated before scan started */
}
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(tuple)))
return HeapTupleBeingUpdated;
if (!TransactionIdDidCommit(HeapTupleHeaderGetXmax(tuple)))
{
/* it must have aborted or crashed */
tuple->t_infomask |= HEAP_XMAX_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
return HeapTupleMayBeUpdated;
}
/* xmax transaction committed */
if (tuple->t_infomask & HEAP_IS_LOCKED)
{
tuple->t_infomask |= HEAP_XMAX_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
return HeapTupleMayBeUpdated;
}
tuple->t_infomask |= HEAP_XMAX_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
return HeapTupleUpdated; /* updated by other */
}
/*
* HeapTupleSatisfiesDirty
* True iff heap tuple is valid including effects of open transactions.
*
* Here, we consider the effects of:
* all committed and in-progress transactions (as of the current instant)
* previous commands of this transaction
* changes made by the current command
*
* This is essentially like HeapTupleSatisfiesItself 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.
*
* Returns extra information in the global variable SnapshotDirty, namely
* xids of concurrent xacts that affected the tuple. SnapshotDirty->xmin
* is set to InvalidTransactionId if xmin is either committed good or
* committed dead; or to xmin if that transaction is still in progress.
* Similarly for SnapshotDirty->xmax.
*/
bool
HeapTupleSatisfiesDirty(Relation relation, HeapTupleHeader tuple, Buffer buffer)
{
const bool disableTupleHints = gp_disable_tuple_hints;
WATCH_VISIBILITY_CLEAR();
SnapshotDirty->xmin = SnapshotDirty->xmax = InvalidTransactionId;
if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED))
{
if (tuple->t_infomask & HEAP_XMIN_INVALID)
return false;
if (tuple->t_infomask & HEAP_MOVED_OFF)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
if (TransactionIdIsCurrentTransactionId(xvac))
return false;
if (!TransactionIdIsInProgress(xvac))
{
if (TransactionIdDidCommit(xvac))
{
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
return false;
}
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
}
}
else if (tuple->t_infomask & HEAP_MOVED_IN)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
if (!TransactionIdIsCurrentTransactionId(xvac))
{
if (TransactionIdIsInProgress(xvac))
return false;
if (TransactionIdDidCommit(xvac))
{
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
}
else
{
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
return false;
}
}
}
else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple)))
{
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
return true;
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
/* deleting subtransaction aborted? */
if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple)))
{
tuple->t_infomask |= HEAP_XMAX_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
return true;
}
Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)));
return false;
}
else if (TransactionIdIsInProgress(HeapTupleHeaderGetXmin(tuple)))
{
SnapshotDirty->xmin = HeapTupleHeaderGetXmin(tuple);
/* XXX shouldn't we fall through to look at xmax? */
return true; /* in insertion by other */
}
else if (TransactionIdDidCommit(HeapTupleHeaderGetXmin(tuple)))
{
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
}
else
{
/* it must have aborted or crashed */
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
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 (tuple->t_infomask & HEAP_IS_LOCKED)
return true;
return false; /* updated by other */
}
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
/* MultiXacts are currently only allowed to lock tuples */
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
return true;
}
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
{
if (tuple->t_infomask & HEAP_IS_LOCKED)
return true;
return false;
}
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(tuple)))
{
SnapshotDirty->xmax = HeapTupleHeaderGetXmax(tuple);
return true;
}
if (!TransactionIdDidCommit(HeapTupleHeaderGetXmax(tuple)))
{
/* it must have aborted or crashed */
tuple->t_infomask |= HEAP_XMAX_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
return true;
}
/* xmax transaction committed */
if (tuple->t_infomask & HEAP_IS_LOCKED)
{
tuple->t_infomask |= HEAP_XMAX_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
return true;
}
tuple->t_infomask |= HEAP_XMAX_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
return false; /* updated by other */
}
/*
* HeapTupleSatisfiesSnapshot
* True iff heap tuple is valid for the given snapshot.
*
* Here, we consider the effects of:
* all transactions committed as of the time of the given snapshot
* previous commands of this transaction
*
* Does _not_ include:
* transactions shown as in-progress by the snapshot
* transactions started after the snapshot was taken
* changes made by the current command
*
* This is the same as HeapTupleSatisfiesNow, except that transactions that
* were in progress or as yet unstarted when the snapshot was taken will
* be treated as uncommitted, even if they have committed by now.
*
* (Notice, however, that the tuple status hint bits will be updated on the
* basis of the true state of the transaction, even if we then pretend we
* can't see it.)
*/
bool
HeapTupleSatisfiesSnapshot(Relation relation, HeapTupleHeader tuple, Snapshot snapshot,
Buffer buffer)
{
const bool disableTupleHints = gp_disable_tuple_hints;
bool inSnapshot = false;
bool setDistributedSnapshotIgnore = false;
WATCH_VISIBILITY_CLEAR();
if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_NOT_HINT_COMMITTED);
if (tuple->t_infomask & HEAP_XMIN_INVALID)
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_ABORTED);
return false;
}
if (tuple->t_infomask & HEAP_MOVED_OFF)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_MOVED_AWAY_BY_VACUUM);
if (TransactionIdIsCurrentTransactionId(xvac))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_VACUUM_XID_CURRENT);
return false;
}
if (!TransactionIdIsInProgress(xvac))
{
if (TransactionIdDidCommit(xvac))
{
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMIN_VACUUM_MOVED_INVALID);
return false;
}
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMIN_COMMITTED);
}
}
else if (tuple->t_infomask & HEAP_MOVED_IN)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_MOVED_IN_BY_VACUUM);
if (!TransactionIdIsCurrentTransactionId(xvac))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_VACUUM_XID_NOT_CURRENT);
if (TransactionIdIsInProgress(xvac))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_VACUUM_XID_IN_PROGRESS);
return false;
}
if (TransactionIdDidCommit(xvac))
{
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMIN_COMMITTED);
}
else
{
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMIN_ABORTED);
return false;
}
}
}
else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple)))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_CURRENT);
#ifdef WATCH_VISIBILITY_IN_ACTION
strcpy(WatchVisibilityXminCurrent, WatchCurrentTransactionString());
#endif
if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_INSERTED_AFTER_SCAN_STARTED);
return false; /* inserted after scan started */
}
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMAX_INVALID);
return true;
}
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_LOCKED);
return true;
}
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
/* deleting subtransaction aborted? */
/* FIXME -- is this correct w.r.t. the cmax of the tuple? */
if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple)))
{
tuple->t_infomask |= HEAP_XMAX_INVALID;
/* If ComboCID format cid, roll it back to normal Cmin */
if (tuple->t_infomask & HEAP_COMBOCID)
{
HeapTupleHeaderSetCmin(tuple, HeapTupleHeaderGetCmin(tuple));
}
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMAX_ABORTED);
return true;
}
/*
* MPP-8317: cursors can't always *tell* that this is the current transaction.
*/
Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)));
if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_DELETED_AFTER_SCAN_STARTED);
return true; /* deleted after scan started */
}
else
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_DELETED_BEFORE_SCAN_STARTED);
return false; /* deleted before scan started */
}
}
else
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_NOT_CURRENT);
#ifdef WATCH_VISIBILITY_IN_ACTION
strcpy(WatchVisibilityXminCurrent, WatchCurrentTransactionString());
#endif
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmin(tuple)))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMIN_IN_PROGRESS);
return false;
}
else if (TransactionIdDidCommit(HeapTupleHeaderGetXmin(tuple)))
{
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMIN_COMMITTED);
}
else
{
/* it must have aborted or crashed */
tuple->t_infomask |= HEAP_XMIN_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMIN_ABORTED);
return false;
}
}
}
/*
* By here, the inserting transaction has committed - have to check
* when...
*/
inSnapshot = XidInSnapshot(
HeapTupleHeaderGetXmin(tuple),
snapshot,
/* isXmax */ false,
((tuple->t_infomask2 & HEAP_XMIN_DISTRIBUTED_SNAPSHOT_IGNORE) != 0),
&setDistributedSnapshotIgnore);
if (setDistributedSnapshotIgnore)
{
tuple->t_infomask2 |= HEAP_XMIN_DISTRIBUTED_SNAPSHOT_IGNORE;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ true);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMIN_DISTRIBUTED_SNAPSHOT_IGNORE);
}
if (inSnapshot)
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SNAPSHOT_SAYS_XMIN_IN_PROGRESS);
return false; /* treat as still in progress */
}
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMAX_INVALID_OR_ABORTED);
return true;
}
if (tuple->t_infomask & HEAP_IS_LOCKED)
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_LOCKED);
return true;
}
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMAX_MULTIXACT);
/* MultiXacts are currently only allowed to lock tuples */
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
return true;
}
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMAX_NOT_HINT_COMMITTED);
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMAX_CURRENT);
if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_DELETED_AFTER_SCAN_STARTED);
return true; /* deleted after scan started */
}
else
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_DELETED_BEFORE_SCAN_STARTED);
return false; /* deleted before scan started */
}
}
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(tuple)))
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_XMAX_IN_PROGRESS);
return true;
}
if (!TransactionIdDidCommit(HeapTupleHeaderGetXmax(tuple)))
{
/* it must have aborted or crashed */
tuple->t_infomask |= HEAP_XMAX_INVALID;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMAX_ABORTED);
return true;
}
/* xmax transaction committed */
tuple->t_infomask |= HEAP_XMAX_COMMITTED;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMAX_COMMITTED);
}
/*
* OK, the deleting transaction committed too ... but when?
*/
inSnapshot = XidInSnapshot(
HeapTupleHeaderGetXmax(tuple),
snapshot,
/* isXmax */ true,
((tuple->t_infomask2 & HEAP_XMAX_DISTRIBUTED_SNAPSHOT_IGNORE) != 0),
&setDistributedSnapshotIgnore);
if (setDistributedSnapshotIgnore)
{
tuple->t_infomask2 |= HEAP_XMAX_DISTRIBUTED_SNAPSHOT_IGNORE;
markDirty(buffer, disableTupleHints, relation, tuple, /* isXmin */ false);
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SET_XMAX_DISTRIBUTED_SNAPSHOT_IGNORE);
}
if (inSnapshot)
{
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SNAPSHOT_SAYS_XMAX_IN_PROGRESS);
return true; /* treat as still in progress */
}
WATCH_VISIBILITY_ADD(WATCH_VISIBILITY_SNAPSHOT_SAYS_XMAX_VISIBLE);
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(HeapTupleHeader tuple, TransactionId OldestXmin,
Buffer buffer, bool vacuumFull)
{
WATCH_VISIBILITY_CLEAR();
/*
* 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 (!(tuple->t_infomask & HEAP_XMIN_COMMITTED))
{
if (tuple->t_infomask & HEAP_XMIN_INVALID)
{
return HEAPTUPLE_DEAD;
}
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))
{
tuple->t_infomask |= HEAP_XMIN_INVALID;
SetBufferCommitInfoNeedsSave(buffer);
return HEAPTUPLE_DEAD;
}
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
SetBufferCommitInfoNeedsSave(buffer);
}
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))
{
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
SetBufferCommitInfoNeedsSave(buffer);
}
else
{
tuple->t_infomask |= HEAP_XMIN_INVALID;
SetBufferCommitInfoNeedsSave(buffer);
return HEAPTUPLE_DEAD;
}
}
else if (TransactionIdIsInProgress(HeapTupleHeaderGetXmin(tuple)))
{
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return HEAPTUPLE_INSERT_IN_PROGRESS;
if (tuple->t_infomask & HEAP_IS_LOCKED)
return HEAPTUPLE_INSERT_IN_PROGRESS;
/* inserted and then deleted by same xact */
return HEAPTUPLE_DELETE_IN_PROGRESS;
}
else if (TransactionIdDidCommit(HeapTupleHeaderGetXmin(tuple)))
{
tuple->t_infomask |= HEAP_XMIN_COMMITTED;
SetBufferCommitInfoNeedsSave(buffer);
}
else
{
/*
* Not in Progress, Not Committed, so either Aborted or crashed
*/
tuple->t_infomask |= HEAP_XMIN_INVALID;
SetBufferCommitInfoNeedsSave(buffer);
return HEAPTUPLE_DEAD;
}
/* Should only get here if we set XMIN_COMMITTED */
Assert(tuple->t_infomask & HEAP_XMIN_COMMITTED);
}
/*
* 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 (tuple->t_infomask & HEAP_IS_LOCKED)
{
/*
* "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. Also, marking dead
* MultiXacts as invalid here provides defense against MultiXactId
* wraparound (see also comments in heap_freeze_tuple()).
*/
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
{
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
if (MultiXactIdIsRunning(HeapTupleHeaderGetXmax(tuple)))
return HEAPTUPLE_LIVE;
}
else
{
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(tuple)))
return HEAPTUPLE_LIVE;
}
/*
* 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.
*/
tuple->t_infomask |= HEAP_XMAX_INVALID;
SetBufferCommitInfoNeedsSave(buffer);
}
return HEAPTUPLE_LIVE;
}
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
/* MultiXacts are currently only allowed to lock tuples */
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
return HEAPTUPLE_LIVE;
}
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
{
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(tuple)))
{
return HEAPTUPLE_DELETE_IN_PROGRESS;
}
else if (TransactionIdDidCommit(HeapTupleHeaderGetXmax(tuple)))
{
tuple->t_infomask |= HEAP_XMAX_COMMITTED;
SetBufferCommitInfoNeedsSave(buffer);
}
else
{
/*
* Not in Progress, Not Committed, so either Aborted or crashed
*/
tuple->t_infomask |= HEAP_XMAX_INVALID;
SetBufferCommitInfoNeedsSave(buffer);
return HEAPTUPLE_LIVE;
}
/* Should only get here if we set XMAX_COMMITTED */
Assert(tuple->t_infomask & HEAP_XMAX_COMMITTED);
}
/*
* Deleter committed, but check special cases.
*/
if (TransactionIdEquals(HeapTupleHeaderGetXmin(tuple),
HeapTupleHeaderGetXmax(tuple)))
{
/*
* Inserter also deleted it, so it was never visible to anyone else.
* However, we can only remove it early if it's not an updated tuple;
* else its parent tuple is linking to it via t_ctid, and this tuple
* mustn't go away before the parent does.
*/
if (!(tuple->t_infomask & HEAP_UPDATED))
return HEAPTUPLE_DEAD;
}
if (!TransactionIdPrecedes(HeapTupleHeaderGetXmax(tuple), OldestXmin))
{
/* deleting xact is too recent, tuple could still be visible */
return HEAPTUPLE_RECENTLY_DEAD;
}
/* Otherwise, it's dead and removable */
return HEAPTUPLE_DEAD;
}
/*
* GetTransactionSnapshot
* Get the appropriate snapshot for a new query in a transaction.
*
* The SerializableSnapshot is the first one taken in a transaction.
* In serializable mode we just use that one throughout the transaction.
* In read-committed mode, we take a new snapshot each time we are called.
*
* Note that the return value points at static storage that will be modified
* by future calls and by CommandCounterIncrement(). Callers should copy
* the result with CopySnapshot() if it is to be used very long.
*/
Snapshot
GetTransactionSnapshot(void)
{
/* First call in transaction? */
if (SerializableSnapshot == NULL)
{
SerializableSnapshot = GetSnapshotData(&SerializableSnapshotData, true);
return SerializableSnapshot;
}
if (IsXactIsoLevelSerializable)
{
return SerializableSnapshot;
}
LatestSnapshot = GetSnapshotData(&LatestSnapshotData, false);
return LatestSnapshot;
}
/*
* GetLatestSnapshot
* Get a snapshot that is up-to-date as of the current instant,
* even if we are executing in SERIALIZABLE mode.
*/
Snapshot
GetLatestSnapshot(void)
{
/* Should not be first call in transaction */
if (SerializableSnapshot == NULL)
elog(ERROR, "no snapshot has been set");
LatestSnapshot = GetSnapshotData(&LatestSnapshotData, false);
return LatestSnapshot;
}
/*
* CopySnapshot
* Copy the given snapshot.
*
* The copy is palloc'd in the current memory context.
*
* Note that this will not work on "special" snapshots.
*/
Snapshot
CopySnapshot(Snapshot snapshot)
{
Snapshot newsnap;
Size subxipoff;
Size size;
if (!IsMVCCSnapshot(snapshot))
return snapshot;
/* We allocate any XID arrays needed in the same palloc block. */
size = subxipoff = sizeof(SnapshotData) +
snapshot->xcnt * sizeof(TransactionId);
if (snapshot->subxcnt > 0)
size += snapshot->subxcnt * sizeof(TransactionId);
newsnap = (Snapshot) palloc(size);
memcpy(newsnap, snapshot, sizeof(SnapshotData));
/* setup XID array */
if (snapshot->xcnt > 0)
{
newsnap->xip = (TransactionId *) (newsnap + 1);
memcpy(newsnap->xip, snapshot->xip,
snapshot->xcnt * sizeof(TransactionId));
}
else
newsnap->xip = NULL;
/* setup subXID array */
if (snapshot->subxcnt > 0)
{
newsnap->subxip = (TransactionId *) ((char *) newsnap + subxipoff);
memcpy(newsnap->subxip, snapshot->subxip,
snapshot->subxcnt * sizeof(TransactionId));
}
else
newsnap->subxip = NULL;
return newsnap;
}
/*
* FreeSnapshot
* Free a snapshot previously copied with CopySnapshot.
*
* This is currently identical to pfree, but is provided for cleanliness.
*
* Do *not* apply this to the results of GetTransactionSnapshot or
* GetLatestSnapshot.
*/
void
FreeSnapshot(Snapshot snapshot)
{
if (!IsMVCCSnapshot(snapshot))
return;
pfree(snapshot);
}
/*
* FreeXactSnapshot
* Free snapshot(s) at end of transaction.
*/
void
FreeXactSnapshot(void)
{
/*
* We do not free the xip arrays for the static snapshot structs; they
* will be reused soon. So this is now just a state change to prevent
* outside callers from accessing the snapshots.
*/
SerializableSnapshot = NULL;
LatestSnapshot = NULL;
ActiveSnapshot = NULL; /* just for cleanliness */
}
struct mpp_xid_map_entry {
pid_t pid;
TransactionId global;
TransactionId local;
};
struct mpp_xid_map {
int size;
int cur;
struct mpp_xid_map_entry map[1];
};
/*
* mpp_global_xid_map
*/
Datum
mpp_global_xid_map(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
struct mpp_xid_map *ctx;
bool nulls[3];
int i, j;
if (SRF_IS_FIRSTCALL())
{
TupleDesc tupdesc;
MemoryContext oldcontext;
volatile SharedSnapshotStruct *arrayP = sharedSnapshotArray;
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
/*
* switch to memory context appropriate for multiple function calls
*/
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* build tupdesc for result tuples */
/* this had better match pg_locks view in system_views.sql */
tupdesc = CreateTemplateTupleDesc(3, false);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "localxid",
XIDOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "globalxid",
XIDOID, -1, 0);
funcctx->tuple_desc = BlessTupleDesc(tupdesc);
/* figure out how many slots we need */
ctx = (struct mpp_xid_map *)palloc0(sizeof(struct mpp_xid_map) +
arrayP->maxSlots * sizeof(struct mpp_xid_map_entry));
j = 0;
LWLockAcquire(SharedSnapshotLock, LW_EXCLUSIVE);
for (i = 0; i < arrayP->maxSlots; i++)
{
SharedSnapshotSlot *testSlot = &arrayP->slots[i];
if (testSlot->slotid != -1)
{
ctx->map[j].pid = testSlot->pid;
ctx->map[j].local = testSlot->xid;
ctx->map[j].global = testSlot->QDxid;
j++;
}
}
LWLockRelease(SharedSnapshotLock);
ctx->size = j;
funcctx->user_fctx = (void *)ctx;
MemoryContextSwitchTo(oldcontext);
}
funcctx = SRF_PERCALL_SETUP();
ctx = (struct mpp_xid_map *)funcctx->user_fctx;
MemSet(nulls, false, sizeof(nulls));
while (ctx->cur < ctx->size)
{
Datum values[3];
HeapTuple tuple;
Datum result;
/*
* Form tuple with appropriate data.
*/
MemSet(values, 0, sizeof(values));
values[0] = Int32GetDatum(ctx->map[ctx->cur].pid);
values[1] = TransactionIdGetDatum(ctx->map[ctx->cur].local);
values[2] = TransactionIdGetDatum(ctx->map[ctx->cur].global);
ctx->cur++;
tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
result = HeapTupleGetDatum(tuple);
SRF_RETURN_NEXT(funcctx, result);
}
SRF_RETURN_DONE(funcctx);
}
/*
* XidInSnapshot
* Is the given XID still-in-progress according to the distributed
* and local snapshots?
*/
static bool
XidInSnapshot(
TransactionId xid, Snapshot snapshot, bool isXmax,
bool distributedSnapshotIgnore, bool *setDistributedSnapshotIgnore)
{
*setDistributedSnapshotIgnore = false;
return XidInSnapshot_Local(xid, snapshot, isXmax);
}
/*
* XidInSnapshot_Local
* Is the given XID still-in-progress according to the local snapshot?
*
* Note: GetSnapshotData never stores either top xid or subxids of our own
* backend into a snapshot, so these xids will not be reported as "running"
* by this function. This is OK for current uses, because we actually only
* apply this for known-committed XIDs.
*/
static bool
XidInSnapshot_Local(TransactionId xid, Snapshot snapshot, bool isXmax)
{
#ifdef WATCH_VISIBILITY_IN_ACTION
TransactionId saveXid = xid;
#endif
uint32 i;
/*
* Make a quick range check to eliminate most XIDs without looking at the
* xip arrays. Note that this is OK even if we convert a subxact XID to
* its parent below, because a subxact with XID < xmin has surely also got
* a parent with XID < xmin, while one with XID >= xmax must belong to a
* parent that was not yet committed at the time of this snapshot.
*/
/* Any xid < xmin is not in-progress */
if (TransactionIdPrecedes(xid, snapshot->xmin))
{
WATCH_VISIBILITY_ADDPAIR(
WATCH_VISIBILITY_XMIN_LESS_THAN_SNAPSHOT_XMIN, isXmax);
return false;
}
/* Any xid >= xmax is in-progress */
if (TransactionIdFollowsOrEquals(xid, snapshot->xmax))
{
WATCH_VISIBILITY_ADDPAIR(
WATCH_VISIBILITY_XMIN_LESS_THAN_SNAPSHOT_XMIN, isXmax);
return true;
}
/*
* If the snapshot contains full subxact data, the fastest way to check
* things is just to compare the given XID against both subxact XIDs and
* top-level XIDs. If the snapshot overflowed, we have to use pg_subtrans
* to convert a subxact XID to its parent XID, but then we need only look
* at top-level XIDs not subxacts.
*/
if (snapshot->subxcnt >= 0)
{
/* full data, so search subxip */
int32 j;
for (j = 0; j < snapshot->subxcnt; j++)
{
if (TransactionIdEquals(xid, snapshot->subxip[j]))
{
WATCH_VISIBILITY_ADDPAIR(
WATCH_VISIBILITY_XMIN_SNAPSHOT_SUBTRANSACTION, isXmax);
return true;
}
}
/* not there, fall through to search xip[] */
}
else
{
/* overflowed, so convert xid to top-level */
xid = SubTransGetTopmostTransaction(xid);
#ifdef WATCH_VISIBILITY_IN_ACTION
if (saveXid != xid)
{
WATCH_VISIBILITY_ADDPAIR(
WATCH_VISIBILITY_XMIN_MAPPED_SUBTRANSACTION, isXmax);
}
#endif
/*
* If xid was indeed a subxact, we might now have an xid < xmin, so
* recheck to avoid an array scan. No point in rechecking xmax.
*/
if (TransactionIdPrecedes(xid, snapshot->xmin))
{
WATCH_VISIBILITY_ADDPAIR(
WATCH_VISIBILITY_XMIN_LESS_THAN_SNAPSHOT_XMIN_2, isXmax);
return false;
}
}
for (i = 0; i < snapshot->xcnt; i++)
{
if (TransactionIdEquals(xid, snapshot->xip[i]))
{
WATCH_VISIBILITY_ADDPAIR(
WATCH_VISIBILITY_XMIN_SNAPSHOT_IN_PROGRESS, isXmax);
return true;
}
}
WATCH_VISIBILITY_ADDPAIR(
WATCH_VISIBILITY_XMIN_SNAPSHOT_NOT_IN_PROGRESS, isXmax);
return false;
}
#ifdef WATCH_VISIBILITY_IN_ACTION
bool WatchVisibilityAllZeros(void)
{
int i;
for (i = 0; i < WATCH_VISIBILITY_BYTE_LEN; i++)
{
if (WatchVisibilityFlags[i] != 0)
return false;
}
return true;
}
static char WatchVisibilityBuffer[2000];
char *
WatchVisibilityInActionString(
BlockNumber page,
OffsetNumber lineoff,
HeapTuple tuple,
Snapshot snapshot)
{
int b;
int count = 0;
CommandId snapshotCurcid = 0;
TransactionId snapshotXmin = InvalidTransactionId;
TransactionId snapshotXmax = InvalidTransactionId;
DistributedTransactionId xminAllDistributedSnapshots = InvalidTransactionId;
DistributedTransactionId xminDistributedSnapshot = InvalidTransactionId;
DistributedTransactionId xmaxDistributedSnapshot = InvalidTransactionId;
char *snapshotStr = NULL;
bool atLeastOne = false;
if (snapshot == SnapshotNow)
snapshotStr = "SnapshotNow";
else if (snapshot == SnapshotSelf)
snapshotStr = "SnapshotSelf";
else if (snapshot == SnapshotAny)
snapshotStr = "SnapshotAny";
else if (snapshot == SnapshotToast)
snapshotStr = "SnapshotToast";
else if (snapshot == SnapshotDirty)
snapshotStr = "SnapshotDirty";
else
{
if (snapshot == LatestSnapshot)
snapshotStr = "LatestSnapshot";
else if (snapshot == SerializableSnapshot)
snapshotStr = "SerializableSnapshot";
else
snapshotStr = "OtherSnapshot";
snapshotCurcid = snapshot->curcid;
snapshotXmin = snapshot->xmin;
snapshotXmax = snapshot->xmax;
}
count += sprintf(&WatchVisibilityBuffer[count],"(%u,%u)", page, lineoff);
count += sprintf(&WatchVisibilityBuffer[count]," xmin %u, xmax %u, %s: ",
HeapTupleHeaderGetXmin(tuple->t_data),
HeapTupleHeaderGetXmax(tuple->t_data),
snapshotStr);
if (WatchVisibilityAllZeros())
{
count += sprintf(&WatchVisibilityBuffer[count],"no visiblity path collected");
return WatchVisibilityBuffer;
}
for (b = 0; b < MAX_WATCH_VISIBILITY; b++)
{
int nthbyte = (b) >> 3;
char nthbit = 1 << ((b) & 7);
if ((WatchVisibilityFlags[nthbyte] & nthbit) != 0)
{
if (atLeastOne)
count += sprintf(&WatchVisibilityBuffer[count], "; ");
switch (b)
{
case WATCH_VISIBILITY_XMIN_NOT_HINT_COMMITTED:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin not hint committed");
break;
case WATCH_VISIBILITY_XMIN_ABORTED:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin aborted");
break;
case WATCH_VISIBILITY_XMIN_MOVED_AWAY_BY_VACUUM:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin moved away by vacuum");
break;
case WATCH_VISIBILITY_VACUUM_XID_CURRENT:
count += sprintf(&WatchVisibilityBuffer[count],"Vacuum xid current");
break;
case WATCH_VISIBILITY_SET_XMIN_VACUUM_MOVED_INVALID:
count += sprintf(&WatchVisibilityBuffer[count],"Set xmin vacuum moved invalid");
break;
case WATCH_VISIBILITY_SET_XMIN_COMMITTED:
count += sprintf(&WatchVisibilityBuffer[count],"Set xmin committed");
break;
case WATCH_VISIBILITY_SET_XMIN_ABORTED:
count += sprintf(&WatchVisibilityBuffer[count],"Set xmin aborted");
break;
case WATCH_VISIBILITY_XMIN_MOVED_IN_BY_VACUUM:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin moved in by vacuum");
break;
case WATCH_VISIBILITY_VACUUM_XID_NOT_CURRENT:
count += sprintf(&WatchVisibilityBuffer[count],"Vacuum xid not current");
break;
case WATCH_VISIBILITY_VACUUM_XID_IN_PROGRESS:
count += sprintf(&WatchVisibilityBuffer[count],"Vacuum xid in progress");
break;
case WATCH_VISIBILITY_XMIN_CURRENT:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin current %s", WatchVisibilityXminCurrent);
break;
case WATCH_VISIBILITY_XMIN_NOT_CURRENT:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin not current %s", WatchVisibilityXminCurrent);
break;
case WATCH_VISIBILITY_XMIN_INSERTED_AFTER_SCAN_STARTED:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin inserted after scan started (curcid %d)",
snapshotCurcid);
break;
case WATCH_VISIBILITY_XMAX_INVALID:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax invalid");
break;
case WATCH_VISIBILITY_LOCKED:
count += sprintf(&WatchVisibilityBuffer[count],"Locked");
break;
case WATCH_VISIBILITY_SET_XMAX_ABORTED:
count += sprintf(&WatchVisibilityBuffer[count],"Set xmax aborted");
break;
case WATCH_VISIBILITY_DELETED_AFTER_SCAN_STARTED:
count += sprintf(&WatchVisibilityBuffer[count],"Deleted after scan started");
break;
case WATCH_VISIBILITY_DELETED_BEFORE_SCAN_STARTED:
count += sprintf(&WatchVisibilityBuffer[count],"Deleted before scan started");
break;
case WATCH_VISIBILITY_XMIN_IN_PROGRESS:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin in progress");
break;
case WATCH_VISIBILITY_SNAPSHOT_SAYS_XMIN_IN_PROGRESS:
count += sprintf(&WatchVisibilityBuffer[count],"Snapshot says xmin in progress");
break;
case WATCH_VISIBILITY_XMAX_INVALID_OR_ABORTED:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax invalid or aborted");
break;
case WATCH_VISIBILITY_XMAX_MULTIXACT:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax multixact");
break;
case WATCH_VISIBILITY_XMAX_NOT_HINT_COMMITTED:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax not hint committed");
break;
case WATCH_VISIBILITY_XMAX_HINT_COMMITTED:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax hint committed");
break;
case WATCH_VISIBILITY_XMAX_CURRENT:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax current");
break;
case WATCH_VISIBILITY_XMAX_IN_PROGRESS:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax in progress");
break;
case WATCH_VISIBILITY_SET_XMAX_COMMITTED:
count += sprintf(&WatchVisibilityBuffer[count],"Set xmax committed");
break;
case WATCH_VISIBILITY_SNAPSHOT_SAYS_XMAX_IN_PROGRESS:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax in progress");
break;
case WATCH_VISIBILITY_SNAPSHOT_SAYS_XMAX_VISIBLE:
count += sprintf(&WatchVisibilityBuffer[count],"Snapshot says xmax visible");
break;
case WATCH_VISIBILITY_USE_DISTRIBUTED_SNAPSHOT_FOR_XMIN:
count += sprintf(&WatchVisibilityBuffer[count],"Use distributed snapshot for xmin");
break;
case WATCH_VISIBILITY_USE_DISTRIBUTED_SNAPSHOT_FOR_XMAX:
count += sprintf(&WatchVisibilityBuffer[count],"Use distributed snapshot for xmax");
break;
case WATCH_VISIBILITY_IGNORE_DISTRIBUTED_SNAPSHOT_FOR_XMIN:
count += sprintf(&WatchVisibilityBuffer[count],"Ignore distributed snapshot for xmin");
break;
case WATCH_VISIBILITY_IGNORE_DISTRIBUTED_SNAPSHOT_FOR_XMAX:
count += sprintf(&WatchVisibilityBuffer[count],"Ignore distributed snapshot for xmax");
break;
case WATCH_VISIBILITY_NO_DISTRIBUTED_SNAPSHOT_FOR_XMIN:
count += sprintf(&WatchVisibilityBuffer[count],"No distributed snapshot for xmin");
break;
case WATCH_VISIBILITY_NO_DISTRIBUTED_SNAPSHOT_FOR_XMAX:
count += sprintf(&WatchVisibilityBuffer[count],"No distributed snapshot for xmax");
break;
case WATCH_VISIBILITY_XMIN_DISTRIBUTED_SNAPSHOT_IN_PROGRESS_FOUND_BY_LOCAL:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin distributed snapshot in progress -- found by local");
break;
case WATCH_VISIBILITY_XMAX_DISTRIBUTED_SNAPSHOT_IN_PROGRESS_FOUND_BY_LOCAL:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax distributed snapshot in progress -- found by local");
break;
case WATCH_VISIBILITY_XMIN_LOCAL_DISTRIBUTED_CACHE_RETURNED_LOCAL:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin in local distributed cache returned local");
break;
case WATCH_VISIBILITY_XMAX_LOCAL_DISTRIBUTED_CACHE_RETURNED_LOCAL:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax in local distributed cache returned local");
break;
case WATCH_VISIBILITY_XMIN_LOCAL_DISTRIBUTED_CACHE_RETURNED_DISTRIB:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin in local distributed cache returned distrib");
break;
case WATCH_VISIBILITY_XMAX_LOCAL_DISTRIBUTED_CACHE_RETURNED_DISTRIB:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax in local distributed cache returned distrib");
break;
case WATCH_VISIBILITY_XMIN_NOT_KNOWN_BY_LOCAL_DISTRIBUTED_XACT:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin not known by local distributed xact module");
break;
case WATCH_VISIBILITY_XMAX_NOT_KNOWN_BY_LOCAL_DISTRIBUTED_XACT:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax not known by local distributed xact module");
break;
case WATCH_VISIBILITY_XMIN_DIFF_DTM_START_IN_DISTRIBUTED_LOG:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin different DTM start in distributed log");
break;
case WATCH_VISIBILITY_XMAX_DIFF_DTM_START_IN_DISTRIBUTED_LOG:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax different DTM start in distributed log");
break;
case WATCH_VISIBILITY_XMIN_FOUND_IN_DISTRIBUTED_LOG:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin found in distributed log");
break;
case WATCH_VISIBILITY_XMAX_FOUND_IN_DISTRIBUTED_LOG:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax found in distributed log");
break;
case WATCH_VISIBILITY_XMIN_KNOWN_LOCAL_IN_DISTRIBUTED_LOG:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin known local in distributed log");
break;
case WATCH_VISIBILITY_XMAX_KNOWN_LOCAL_IN_DISTRIBUTED_LOG:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax known local in distributed log");
break;
case WATCH_VISIBILITY_XMIN_KNOWN_BY_LOCAL_DISTRIBUTED_XACT:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin known by local distributed xact module");
break;
case WATCH_VISIBILITY_XMAX_KNOWN_BY_LOCAL_DISTRIBUTED_XACT:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax known by local distributed xact module");
break;
case WATCH_VISIBILITY_XMIN_LESS_THAN_ALL_CURRENT_DISTRIBUTED:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin < all current distributed %u",
xminAllDistributedSnapshots);
break;
case WATCH_VISIBILITY_XMAX_LESS_THAN_ALL_CURRENT_DISTRIBUTED:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax < all current distributed %u",
xminAllDistributedSnapshots);
break;
case WATCH_VISIBILITY_XMIN_LESS_THAN_DISTRIBUTED_SNAPSHOT_XMIN:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin < distributed snapshot xmin %u",
xminDistributedSnapshot);
break;
case WATCH_VISIBILITY_XMAX_LESS_THAN_DISTRIBUTED_SNAPSHOT_XMIN:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax < distributed snapshot xmin %u",
xminDistributedSnapshot);
break;
case WATCH_VISIBILITY_XMIN_GREATER_THAN_EQUAL_DISTRIBUTED_SNAPSHOT_XMAX:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin >= distributed snapshot xmax %u",
xmaxDistributedSnapshot);
break;
case WATCH_VISIBILITY_XMAX_GREATER_THAN_EQUAL_DISTRIBUTED_SNAPSHOT_XMAX:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax >= distributed snapshot xmax %u",
xmaxDistributedSnapshot);
break;
case WATCH_VISIBILITY_XMIN_DISTRIBUTED_SNAPSHOT_IN_PROGRESS_BY_DISTRIB:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin in distributed snapshot in progress by distrib");
break;
case WATCH_VISIBILITY_XMAX_DISTRIBUTED_SNAPSHOT_IN_PROGRESS_BY_DISTRIB:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax in distributed snapshot in progress by distrib");
break;
case WATCH_VISIBILITY_XMIN_DISTRIBUTED_SNAPSHOT_NOT_IN_PROGRESS:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin not in distributed snapshot in progress");
break;
case WATCH_VISIBILITY_XMAX_DISTRIBUTED_SNAPSHOT_NOT_IN_PROGRESS:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax not in distributed snapshot in progress");
break;
case WATCH_VISIBILITY_XMIN_LESS_THAN_SNAPSHOT_XMIN:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin < snapshot xmin %u",
snapshotXmin);
break;
case WATCH_VISIBILITY_XMAX_LESS_THAN_SNAPSHOT_XMIN:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax < snapshot xmin %u",
snapshotXmin);
break;
case WATCH_VISIBILITY_XMIN_GREATER_THAN_EQUAL_SNAPSHOT_XMAX:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin >= snapshot xmax %u",
snapshotXmax);
break;
case WATCH_VISIBILITY_XMAX_GREATER_THAN_EQUAL_SNAPSHOT_XMAX:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax >= snapshot xmax %u",
snapshotXmax);
break;
case WATCH_VISIBILITY_XMIN_SNAPSHOT_SUBTRANSACTION:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin snapshot subtransaction");
break;
case WATCH_VISIBILITY_XMAX_SNAPSHOT_SUBTRANSACTION:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax snapshot subtransaction");
break;
case WATCH_VISIBILITY_XMIN_MAPPED_SUBTRANSACTION:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin mapped subtransaction");
break;
case WATCH_VISIBILITY_XMAX_MAPPED_SUBTRANSACTION:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax mapped subtransaction");
break;
case WATCH_VISIBILITY_XMIN_LESS_THAN_SNAPSHOT_XMIN_2:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin < snapshot xmin %u (#2)",
snapshotXmin);
break;
case WATCH_VISIBILITY_XMAX_LESS_THAN_SNAPSHOT_XMIN_2:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax < snapshot xmin %u (#2)",
snapshotXmin);
break;
case WATCH_VISIBILITY_XMIN_SNAPSHOT_IN_PROGRESS:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin snapshot in progress");
break;
case WATCH_VISIBILITY_XMAX_SNAPSHOT_IN_PROGRESS:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax snapshot in progress");
break;
case WATCH_VISIBILITY_XMIN_SNAPSHOT_NOT_IN_PROGRESS:
count += sprintf(&WatchVisibilityBuffer[count],"Xmin snapshot not in progress");
break;
case WATCH_VISIBILITY_XMAX_SNAPSHOT_NOT_IN_PROGRESS:
count += sprintf(&WatchVisibilityBuffer[count],"Xmax snapshot not in progress");
break;
case WATCH_VISIBILITY_SET_XMIN_DISTRIBUTED_SNAPSHOT_IGNORE:
count += sprintf(&WatchVisibilityBuffer[count],"Set xmin distributed snapshot ignore");
break;
case WATCH_VISIBILITY_SET_XMAX_DISTRIBUTED_SNAPSHOT_IGNORE:
count += sprintf(&WatchVisibilityBuffer[count],"Set xmax distributed snapshot ignore");
break;
case WATCH_VISIBILITY_GUC_OFF_MARK_BUFFFER_DIRTY_FOR_XMIN:
count += sprintf(&WatchVisibilityBuffer[count],"(gp_disable_tuple_hints OFF) mark buffer dirty for xmin");
break;
case WATCH_VISIBILITY_GUC_OFF_MARK_BUFFFER_DIRTY_FOR_XMAX:
count += sprintf(&WatchVisibilityBuffer[count],"(gp_disable_tuple_hints OFF) mark buffer dirty for xmax");
break;
case WATCH_VISIBILITY_NO_REL_MARK_BUFFFER_DIRTY_FOR_XMIN:
count += sprintf(&WatchVisibilityBuffer[count],"(gp_disable_tuple_hints ON) no relation -- mark buffer dirty for xmin");
break;
case WATCH_VISIBILITY_NO_REL_MARK_BUFFFER_DIRTY_FOR_XMAX:
count += sprintf(&WatchVisibilityBuffer[count],"(gp_disable_tuple_hints ON) no relation -- mark buffer dirty for xmax");
break;
case WATCH_VISIBILITY_SYS_CAT_MARK_BUFFFER_DIRTY_FOR_XMIN:
count += sprintf(&WatchVisibilityBuffer[count],"(gp_disable_tuple_hints ON) system catalog -- mark buffer dirty for xmin");
break;
case WATCH_VISIBILITY_SYS_CAT_MARK_BUFFFER_DIRTY_FOR_XMAX:
count += sprintf(&WatchVisibilityBuffer[count],"(gp_disable_tuple_hints ON) system catalog -- mark buffer dirty for xmax");
break;
case WATCH_VISIBILITY_NO_XID_MARK_BUFFFER_DIRTY_FOR_XMIN:
count += sprintf(&WatchVisibilityBuffer[count],"(gp_disable_tuple_hints ON) no xid -- mark buffer dirty for xmin");
break;
case WATCH_VISIBILITY_NO_XID_MARK_BUFFFER_DIRTY_FOR_XMAX:
count += sprintf(&WatchVisibilityBuffer[count],"(gp_disable_tuple_hints ON) no xid -- mark buffer dirty for xmax");
break;
case WATCH_VISIBILITY_TOO_OLD_MARK_BUFFFER_DIRTY_FOR_XMIN:
count += sprintf(&WatchVisibilityBuffer[count],"(gp_disable_tuple_hints ON) too old -- mark buffer dirty for xmin");
break;
case WATCH_VISIBILITY_TOO_OLD_MARK_BUFFFER_DIRTY_FOR_XMAX:
count += sprintf(&WatchVisibilityBuffer[count],"(gp_disable_tuple_hints ON) too old -- mark buffer dirty for xmax");
break;
case WATCH_VISIBILITY_YOUNG_DO_NOT_MARK_BUFFFER_DIRTY_FOR_XMIN:
count += sprintf(&WatchVisibilityBuffer[count],"(gp_disable_tuple_hints ON) young -- do not mark buffer dirty for xmin");
break;
case WATCH_VISIBILITY_YOUNG_DO_NOT_MARK_BUFFFER_DIRTY_FOR_XMAX:
count += sprintf(&WatchVisibilityBuffer[count],"(gp_disable_tuple_hints ON) young -- do not mark buffer dirty for xmax");
break;
default:
count += sprintf(&WatchVisibilityBuffer[count],"<Unknown %d>", b);
break;
}
atLeastOne = true;
}
}
return WatchVisibilityBuffer;
}
#endif
static char *TupleTransactionStatus_Name(TupleTransactionStatus status)
{
switch (status)
{
case TupleTransactionStatus_None: return "None";
case TupleTransactionStatus_Frozen: return "Frozen";
case TupleTransactionStatus_HintCommitted: return "Hint-Committed";
case TupleTransactionStatus_HintAborted: return "Hint-Aborted";
case TupleTransactionStatus_CLogInProgress: return "CLog-In-Progress";
case TupleTransactionStatus_CLogCommitted: return "CLog-Committed";
case TupleTransactionStatus_CLogAborted: return "CLog-Aborted";
case TupleTransactionStatus_CLogSubCommitted: return "CLog-Sub-Committed";
default:
return "Unknown";
}
}
static char *TupleVisibilityStatus_Name(TupleVisibilityStatus status)
{
switch (status)
{
case TupleVisibilityStatus_Unknown: return "Unknown";
case TupleVisibilityStatus_InProgress: return "In-Progress";
case TupleVisibilityStatus_Aborted: return "Aborted";
case TupleVisibilityStatus_Past: return "Past";
case TupleVisibilityStatus_Now: return "Now";
default:
return "Unknown";
}
}
static TupleTransactionStatus GetTupleVisibilityCLogStatus(TransactionId xid)
{
XidStatus xidStatus;
xidStatus = TransactionIdGetStatus(xid);
switch (xidStatus)
{
case TRANSACTION_STATUS_IN_PROGRESS: return TupleTransactionStatus_CLogInProgress;
case TRANSACTION_STATUS_COMMITTED: return TupleTransactionStatus_CLogCommitted;
case TRANSACTION_STATUS_ABORTED: return TupleTransactionStatus_CLogAborted;
case TRANSACTION_STATUS_SUB_COMMITTED: return TupleTransactionStatus_CLogSubCommitted;
default:
// Never gets here. XidStatus is only 2-bits.
return TupleTransactionStatus_None;
}
}
void GetTupleVisibilitySummary(
HeapTuple tuple,
TupleVisibilitySummary *tupleVisibilitySummary)
{
tupleVisibilitySummary->tid = tuple->t_self;
tupleVisibilitySummary->infomask = tuple->t_data->t_infomask;
tupleVisibilitySummary->infomask2 = tuple->t_data->t_infomask2;
tupleVisibilitySummary->updateTid = tuple->t_data->t_ctid;
tupleVisibilitySummary->xmin = HeapTupleHeaderGetXmin(tuple->t_data);
if (!TransactionIdIsNormal(tupleVisibilitySummary->xmin))
{
if (tupleVisibilitySummary->xmin == FrozenTransactionId)
{
tupleVisibilitySummary->xminStatus = TupleTransactionStatus_Frozen;
}
else
{
tupleVisibilitySummary->xminStatus = TupleTransactionStatus_None;
}
}
else if (tuple->t_data->t_infomask & HEAP_XMIN_COMMITTED)
{
tupleVisibilitySummary->xminStatus = TupleTransactionStatus_HintCommitted;
}
else if (tuple->t_data->t_infomask & HEAP_XMIN_INVALID)
{
tupleVisibilitySummary->xminStatus = TupleTransactionStatus_HintAborted;
}
else
{
tupleVisibilitySummary->xminStatus =
GetTupleVisibilityCLogStatus(tupleVisibilitySummary->xmin);
}
tupleVisibilitySummary->xmax = HeapTupleHeaderGetXmax(tuple->t_data);
if (!TransactionIdIsNormal(tupleVisibilitySummary->xmax))
{
if (tupleVisibilitySummary->xmax == FrozenTransactionId)
{
tupleVisibilitySummary->xmaxStatus = TupleTransactionStatus_Frozen;
}
else
{
tupleVisibilitySummary->xmaxStatus = TupleTransactionStatus_None;
}
}
else if (tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED)
{
tupleVisibilitySummary->xmaxStatus = TupleTransactionStatus_HintCommitted;
}
else if (tuple->t_data->t_infomask & HEAP_XMAX_INVALID)
{
tupleVisibilitySummary->xmaxStatus = TupleTransactionStatus_HintAborted;
}
else
{
tupleVisibilitySummary->xmaxStatus =
GetTupleVisibilityCLogStatus(tupleVisibilitySummary->xmax);
}
tupleVisibilitySummary->cid =
HeapTupleHeaderGetRawCommandId(tuple->t_data);
/*
* Evaluate xmin and xmax status to produce overall visibility.
*
* UNDONE: Too simplistic?
*/
switch (tupleVisibilitySummary->xminStatus)
{
case TupleTransactionStatus_None:
case TupleTransactionStatus_CLogSubCommitted:
tupleVisibilitySummary->visibilityStatus = TupleVisibilityStatus_Unknown;
break;
case TupleTransactionStatus_CLogInProgress:
tupleVisibilitySummary->visibilityStatus = TupleVisibilityStatus_InProgress;
break;
case TupleTransactionStatus_HintAborted:
case TupleTransactionStatus_CLogAborted:
tupleVisibilitySummary->visibilityStatus = TupleVisibilityStatus_Aborted;
break;
case TupleTransactionStatus_Frozen:
case TupleTransactionStatus_HintCommitted:
case TupleTransactionStatus_CLogCommitted:
{
switch (tupleVisibilitySummary->xmaxStatus)
{
case TupleTransactionStatus_None:
case TupleTransactionStatus_Frozen:
case TupleTransactionStatus_CLogInProgress:
case TupleTransactionStatus_HintAborted:
case TupleTransactionStatus_CLogAborted:
tupleVisibilitySummary->visibilityStatus = TupleVisibilityStatus_Now;
break;
case TupleTransactionStatus_CLogSubCommitted:
tupleVisibilitySummary->visibilityStatus = TupleVisibilityStatus_Unknown;
break;
case TupleTransactionStatus_HintCommitted:
case TupleTransactionStatus_CLogCommitted:
tupleVisibilitySummary->visibilityStatus = TupleVisibilityStatus_Past;
break;
default:
elog(ERROR, "Unrecognized tuple transaction status: %d",
(int) tupleVisibilitySummary->xmaxStatus);
tupleVisibilitySummary->visibilityStatus = TupleVisibilityStatus_Unknown;
break;
}
}
break;
default:
elog(ERROR, "Unrecognized tuple transaction status: %d",
(int) tupleVisibilitySummary->xminStatus);
tupleVisibilitySummary->visibilityStatus = TupleVisibilityStatus_Unknown;
break;
}
}
static void TupleVisibilityAddFlagName(
StringInfoData *buf,
int16 rawFlag,
char *flagName,
bool *atLeastOne)
{
if (rawFlag != 0)
{
if (*atLeastOne)
{
appendStringInfo(buf, ", ");
}
appendStringInfo(buf, "%s", flagName);
*atLeastOne = true;
}
}
static char *GetTupleVisibilityInfoMaskSet(
int16 infomask,
int16 infomask2)
{
StringInfoData buf;
bool atLeastOne;
initStringInfo(&buf);
appendStringInfo(&buf, "{");
atLeastOne = false;
TupleVisibilityAddFlagName(&buf, infomask & HEAP_COMBOCID, "HEAP_COMBOCID", &atLeastOne);
TupleVisibilityAddFlagName(&buf, infomask & HEAP_XMAX_EXCL_LOCK, "HEAP_XMAX_EXCL_LOCK", &atLeastOne);
TupleVisibilityAddFlagName(&buf, infomask & HEAP_XMAX_SHARED_LOCK, "HEAP_XMAX_SHARED_LOCK", &atLeastOne);
TupleVisibilityAddFlagName(&buf, infomask & HEAP_XMIN_COMMITTED, "HEAP_XMIN_COMMITTED", &atLeastOne);
TupleVisibilityAddFlagName(&buf, infomask & HEAP_XMIN_INVALID, "HEAP_XMIN_INVALID", &atLeastOne);
TupleVisibilityAddFlagName(&buf, infomask & HEAP_XMAX_COMMITTED, "HEAP_XMAX_COMMITTED", &atLeastOne);
TupleVisibilityAddFlagName(&buf, infomask & HEAP_XMAX_INVALID, "HEAP_XMAX_INVALID", &atLeastOne);
TupleVisibilityAddFlagName(&buf, infomask & HEAP_XMAX_IS_MULTI, "HEAP_XMAX_IS_MULTI", &atLeastOne);
TupleVisibilityAddFlagName(&buf, infomask & HEAP_UPDATED, "HEAP_UPDATED", &atLeastOne);
TupleVisibilityAddFlagName(&buf, infomask & HEAP_MOVED_OFF, "HEAP_MOVED_OFF", &atLeastOne);
TupleVisibilityAddFlagName(&buf, infomask & HEAP_MOVED_IN, "HEAP_MOVED_IN", &atLeastOne);
TupleVisibilityAddFlagName(&buf, infomask2 & HEAP_XMIN_DISTRIBUTED_SNAPSHOT_IGNORE, "HEAP_XMIN_DISTRIBUTED_SNAPSHOT_IGNORE", &atLeastOne);
TupleVisibilityAddFlagName(&buf, infomask2 & HEAP_XMAX_DISTRIBUTED_SNAPSHOT_IGNORE, "HEAP_XMAX_DISTRIBUTED_SNAPSHOT_IGNORE", &atLeastOne);
appendStringInfo(&buf, "}");
return buf.data;
}
// 0 gp_tid TIDOID
// 1 gp_xmin INT4OID
// 2 gp_xmin_status TEXTOID
// 3 gp_xmin_commit_distrib_id TEXTOID
// 4 gp_xmax INT4OID
// 5 gp_xmax_status TEXTOID
// 6 gp_xmax_distrib_id TEXTOID
// 7 gp_command_id INT4OID
// 8 gp_infomask TEXTOID
// 9 gp_update_tid TIDOID
// 10 gp_visibility TEXTOID
void GetTupleVisibilitySummaryDatums(
Datum *values,
bool *nulls,
TupleVisibilitySummary *tupleVisibilitySummary)
{
char *infoMaskSet;
values[0] = ItemPointerGetDatum(&tupleVisibilitySummary->tid);
values[1] = Int32GetDatum((int32)tupleVisibilitySummary->xmin);
values[2] =
DirectFunctionCall1(textin,
CStringGetDatum(
TupleTransactionStatus_Name(
tupleVisibilitySummary->xminStatus)));
nulls[3] = true;
values[4] = Int32GetDatum((int32)tupleVisibilitySummary->xmax);
values[5] =
DirectFunctionCall1(textin,
CStringGetDatum(
TupleTransactionStatus_Name(
tupleVisibilitySummary->xmaxStatus)));
nulls[6] = true;
values[7] = Int32GetDatum((int32)tupleVisibilitySummary->cid);
infoMaskSet = GetTupleVisibilityInfoMaskSet(
tupleVisibilitySummary->infomask,
tupleVisibilitySummary->infomask2);
values[8] =
DirectFunctionCall1(textin,
CStringGetDatum(
infoMaskSet));
pfree(infoMaskSet);
values[9] = ItemPointerGetDatum(&tupleVisibilitySummary->updateTid);
values[10] =
DirectFunctionCall1(textin,
CStringGetDatum(
TupleVisibilityStatus_Name(
tupleVisibilitySummary->visibilityStatus)));
}
char *GetTupleVisibilitySummaryString(
TupleVisibilitySummary *tupleVisibilitySummary)
{
StringInfoData buf;
char *infoMaskSet;
initStringInfo(&buf);
appendStringInfo(&buf, "tid %s",
ItemPointerToString(&tupleVisibilitySummary->tid));
appendStringInfo(&buf, ", xmin %u",
tupleVisibilitySummary->xmin);
appendStringInfo(&buf, ", xmin_status '%s'",
TupleTransactionStatus_Name(
tupleVisibilitySummary->xminStatus));
appendStringInfo(&buf, ", xmax %u",
tupleVisibilitySummary->xmax);
appendStringInfo(&buf, ", xmax_status '%s'",
TupleTransactionStatus_Name(
tupleVisibilitySummary->xmaxStatus));
appendStringInfo(&buf, ", command_id %u",
tupleVisibilitySummary->cid);
infoMaskSet = GetTupleVisibilityInfoMaskSet(
tupleVisibilitySummary->infomask,
tupleVisibilitySummary->infomask2);
appendStringInfo(&buf, ", infomask '%s'",
infoMaskSet);
pfree(infoMaskSet);
appendStringInfo(&buf, ", update_tid %s",
ItemPointerToString(&tupleVisibilitySummary->updateTid));
appendStringInfo(&buf, ", visibility '%s'",
TupleVisibilityStatus_Name(
tupleVisibilitySummary->visibilityStatus));
return buf.data;
}