summaryrefslogtreecommitdiffstats
path: root/src/backend/access/transam/transam.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/access/transam/transam.c')
-rw-r--r--src/backend/access/transam/transam.c398
1 files changed, 398 insertions, 0 deletions
diff --git a/src/backend/access/transam/transam.c b/src/backend/access/transam/transam.c
new file mode 100644
index 0000000..5865810
--- /dev/null
+++ b/src/backend/access/transam/transam.c
@@ -0,0 +1,398 @@
+/*-------------------------------------------------------------------------
+ *
+ * transam.c
+ * postgres transaction (commit) log interface routines
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/transam/transam.c
+ *
+ * NOTES
+ * This file contains the high level access-method interface to the
+ * transaction system.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/clog.h"
+#include "access/subtrans.h"
+#include "access/transam.h"
+#include "utils/snapmgr.h"
+
+/*
+ * Single-item cache for results of TransactionLogFetch. It's worth having
+ * such a cache because we frequently find ourselves repeatedly checking the
+ * same XID, for example when scanning a table just after a bulk insert,
+ * update, or delete.
+ */
+static TransactionId cachedFetchXid = InvalidTransactionId;
+static XidStatus cachedFetchXidStatus;
+static XLogRecPtr cachedCommitLSN;
+
+/* Local functions */
+static XidStatus TransactionLogFetch(TransactionId transactionId);
+
+
+/* ----------------------------------------------------------------
+ * Postgres log access method interface
+ *
+ * TransactionLogFetch
+ * ----------------------------------------------------------------
+ */
+
+/*
+ * TransactionLogFetch --- fetch commit status of specified transaction id
+ */
+static XidStatus
+TransactionLogFetch(TransactionId transactionId)
+{
+ XidStatus xidstatus;
+ XLogRecPtr xidlsn;
+
+ /*
+ * Before going to the commit log manager, check our single item cache to
+ * see if we didn't just check the transaction status a moment ago.
+ */
+ if (TransactionIdEquals(transactionId, cachedFetchXid))
+ return cachedFetchXidStatus;
+
+ /*
+ * Also, check to see if the transaction ID is a permanent one.
+ */
+ if (!TransactionIdIsNormal(transactionId))
+ {
+ if (TransactionIdEquals(transactionId, BootstrapTransactionId))
+ return TRANSACTION_STATUS_COMMITTED;
+ if (TransactionIdEquals(transactionId, FrozenTransactionId))
+ return TRANSACTION_STATUS_COMMITTED;
+ return TRANSACTION_STATUS_ABORTED;
+ }
+
+ /*
+ * Get the transaction status.
+ */
+ xidstatus = TransactionIdGetStatus(transactionId, &xidlsn);
+
+ /*
+ * Cache it, but DO NOT cache status for unfinished or sub-committed
+ * transactions! We only cache status that is guaranteed not to change.
+ */
+ if (xidstatus != TRANSACTION_STATUS_IN_PROGRESS &&
+ xidstatus != TRANSACTION_STATUS_SUB_COMMITTED)
+ {
+ cachedFetchXid = transactionId;
+ cachedFetchXidStatus = xidstatus;
+ cachedCommitLSN = xidlsn;
+ }
+
+ return xidstatus;
+}
+
+/* ----------------------------------------------------------------
+ * Interface functions
+ *
+ * TransactionIdDidCommit
+ * TransactionIdDidAbort
+ * ========
+ * these functions test the transaction status of
+ * a specified transaction id.
+ *
+ * TransactionIdCommitTree
+ * TransactionIdAsyncCommitTree
+ * TransactionIdAbortTree
+ * ========
+ * these functions set the transaction status of the specified
+ * transaction tree.
+ *
+ * See also TransactionIdIsInProgress, which once was in this module
+ * but now lives in procarray.c.
+ * ----------------------------------------------------------------
+ */
+
+/*
+ * TransactionIdDidCommit
+ * True iff transaction associated with the identifier did commit.
+ *
+ * Note:
+ * Assumes transaction identifier is valid and exists in clog.
+ */
+bool /* true if given transaction committed */
+TransactionIdDidCommit(TransactionId transactionId)
+{
+ XidStatus xidstatus;
+
+ xidstatus = TransactionLogFetch(transactionId);
+
+ /*
+ * If it's marked committed, it's committed.
+ */
+ if (xidstatus == TRANSACTION_STATUS_COMMITTED)
+ return true;
+
+ /*
+ * If it's marked subcommitted, we have to check the parent recursively.
+ * However, if it's older than TransactionXmin, we can't look at
+ * pg_subtrans; instead assume that the parent crashed without cleaning up
+ * its children.
+ *
+ * Originally we Assert'ed that the result of SubTransGetParent was not
+ * zero. However with the introduction of prepared transactions, there can
+ * be a window just after database startup where we do not have complete
+ * knowledge in pg_subtrans of the transactions after TransactionXmin.
+ * StartupSUBTRANS() has ensured that any missing information will be
+ * zeroed. Since this case should not happen under normal conditions, it
+ * seems reasonable to emit a WARNING for it.
+ */
+ if (xidstatus == TRANSACTION_STATUS_SUB_COMMITTED)
+ {
+ TransactionId parentXid;
+
+ if (TransactionIdPrecedes(transactionId, TransactionXmin))
+ return false;
+ parentXid = SubTransGetParent(transactionId);
+ if (!TransactionIdIsValid(parentXid))
+ {
+ elog(WARNING, "no pg_subtrans entry for subcommitted XID %u",
+ transactionId);
+ return false;
+ }
+ return TransactionIdDidCommit(parentXid);
+ }
+
+ /*
+ * It's not committed.
+ */
+ return false;
+}
+
+/*
+ * TransactionIdDidAbort
+ * True iff transaction associated with the identifier did abort.
+ *
+ * Note:
+ * Assumes transaction identifier is valid and exists in clog.
+ */
+bool /* true if given transaction aborted */
+TransactionIdDidAbort(TransactionId transactionId)
+{
+ XidStatus xidstatus;
+
+ xidstatus = TransactionLogFetch(transactionId);
+
+ /*
+ * If it's marked aborted, it's aborted.
+ */
+ if (xidstatus == TRANSACTION_STATUS_ABORTED)
+ return true;
+
+ /*
+ * If it's marked subcommitted, we have to check the parent recursively.
+ * However, if it's older than TransactionXmin, we can't look at
+ * pg_subtrans; instead assume that the parent crashed without cleaning up
+ * its children.
+ */
+ if (xidstatus == TRANSACTION_STATUS_SUB_COMMITTED)
+ {
+ TransactionId parentXid;
+
+ if (TransactionIdPrecedes(transactionId, TransactionXmin))
+ return true;
+ parentXid = SubTransGetParent(transactionId);
+ if (!TransactionIdIsValid(parentXid))
+ {
+ /* see notes in TransactionIdDidCommit */
+ elog(WARNING, "no pg_subtrans entry for subcommitted XID %u",
+ transactionId);
+ return true;
+ }
+ return TransactionIdDidAbort(parentXid);
+ }
+
+ /*
+ * It's not aborted.
+ */
+ return false;
+}
+
+/*
+ * TransactionIdCommitTree
+ * Marks the given transaction and children as committed
+ *
+ * "xid" is a toplevel transaction commit, and the xids array contains its
+ * committed subtransactions.
+ *
+ * This commit operation is not guaranteed to be atomic, but if not, subxids
+ * are correctly marked subcommit first.
+ */
+void
+TransactionIdCommitTree(TransactionId xid, int nxids, TransactionId *xids)
+{
+ TransactionIdSetTreeStatus(xid, nxids, xids,
+ TRANSACTION_STATUS_COMMITTED,
+ InvalidXLogRecPtr);
+}
+
+/*
+ * TransactionIdAsyncCommitTree
+ * Same as above, but for async commits. The commit record LSN is needed.
+ */
+void
+TransactionIdAsyncCommitTree(TransactionId xid, int nxids, TransactionId *xids,
+ XLogRecPtr lsn)
+{
+ TransactionIdSetTreeStatus(xid, nxids, xids,
+ TRANSACTION_STATUS_COMMITTED, lsn);
+}
+
+/*
+ * TransactionIdAbortTree
+ * Marks the given transaction and children as aborted.
+ *
+ * "xid" is a toplevel transaction commit, and the xids array contains its
+ * committed subtransactions.
+ *
+ * We don't need to worry about the non-atomic behavior, since any onlookers
+ * will consider all the xacts as not-yet-committed anyway.
+ */
+void
+TransactionIdAbortTree(TransactionId xid, int nxids, TransactionId *xids)
+{
+ TransactionIdSetTreeStatus(xid, nxids, xids,
+ TRANSACTION_STATUS_ABORTED, InvalidXLogRecPtr);
+}
+
+/*
+ * TransactionIdPrecedes --- is id1 logically < id2?
+ */
+bool
+TransactionIdPrecedes(TransactionId id1, TransactionId id2)
+{
+ /*
+ * If either ID is a permanent XID then we can just do unsigned
+ * comparison. If both are normal, do a modulo-2^32 comparison.
+ */
+ int32 diff;
+
+ if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
+ return (id1 < id2);
+
+ diff = (int32) (id1 - id2);
+ return (diff < 0);
+}
+
+/*
+ * TransactionIdPrecedesOrEquals --- is id1 logically <= id2?
+ */
+bool
+TransactionIdPrecedesOrEquals(TransactionId id1, TransactionId id2)
+{
+ int32 diff;
+
+ if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
+ return (id1 <= id2);
+
+ diff = (int32) (id1 - id2);
+ return (diff <= 0);
+}
+
+/*
+ * TransactionIdFollows --- is id1 logically > id2?
+ */
+bool
+TransactionIdFollows(TransactionId id1, TransactionId id2)
+{
+ int32 diff;
+
+ if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
+ return (id1 > id2);
+
+ diff = (int32) (id1 - id2);
+ return (diff > 0);
+}
+
+/*
+ * TransactionIdFollowsOrEquals --- is id1 logically >= id2?
+ */
+bool
+TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2)
+{
+ int32 diff;
+
+ if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
+ return (id1 >= id2);
+
+ diff = (int32) (id1 - id2);
+ return (diff >= 0);
+}
+
+
+/*
+ * TransactionIdLatest --- get latest XID among a main xact and its children
+ */
+TransactionId
+TransactionIdLatest(TransactionId mainxid,
+ int nxids, const TransactionId *xids)
+{
+ TransactionId result;
+
+ /*
+ * In practice it is highly likely that the xids[] array is sorted, and so
+ * we could save some cycles by just taking the last child XID, but this
+ * probably isn't so performance-critical that it's worth depending on
+ * that assumption. But just to show we're not totally stupid, scan the
+ * array back-to-front to avoid useless assignments.
+ */
+ result = mainxid;
+ while (--nxids >= 0)
+ {
+ if (TransactionIdPrecedes(result, xids[nxids]))
+ result = xids[nxids];
+ }
+ return result;
+}
+
+
+/*
+ * TransactionIdGetCommitLSN
+ *
+ * This function returns an LSN that is late enough to be able
+ * to guarantee that if we flush up to the LSN returned then we
+ * will have flushed the transaction's commit record to disk.
+ *
+ * The result is not necessarily the exact LSN of the transaction's
+ * commit record! For example, for long-past transactions (those whose
+ * clog pages already migrated to disk), we'll return InvalidXLogRecPtr.
+ * Also, because we group transactions on the same clog page to conserve
+ * storage, we might return the LSN of a later transaction that falls into
+ * the same group.
+ */
+XLogRecPtr
+TransactionIdGetCommitLSN(TransactionId xid)
+{
+ XLogRecPtr result;
+
+ /*
+ * Currently, all uses of this function are for xids that were just
+ * reported to be committed by TransactionLogFetch, so we expect that
+ * checking TransactionLogFetch's cache will usually succeed and avoid an
+ * extra trip to shared memory.
+ */
+ if (TransactionIdEquals(xid, cachedFetchXid))
+ return cachedCommitLSN;
+
+ /* Special XIDs are always known committed */
+ if (!TransactionIdIsNormal(xid))
+ return InvalidXLogRecPtr;
+
+ /*
+ * Get the transaction status.
+ */
+ (void) TransactionIdGetStatus(xid, &result);
+
+ return result;
+}