From 623d385f35f29d9b30a9edc2d49388b708b18382 Mon Sep 17 00:00:00 2001
From: Thomas Munro <tmunro@postgresql.org>
Date: Tue, 28 Jan 2020 17:23:49 +1300
Subject: [PATCH 2/2] Introduce xid8 variants of the txid_XXX() fmgr functions.

The txid_XXX() family of functions exposes 64 bit transaction IDs to
users as int8.  Now that we have an SQL type for FullTransactionId,
define a set of functions xid8_XXX() corresponding to the txid_XXX()
functions.

It's a bit sneaky to use the same C functions for both, but since the
binary representation is identical except for the signedness of the
type, and since older functions were already using the wrong signedness,
and since we'll presumably drop the txid_XXX() variants after a period
of time, it seems reasonable to switch to FullTransactionId internally
and share the code for both.

Author: Thomas Munro
Reviewed-by: Takao Fujii, Yoshikazu Imai, Mark Dilger
Discussion: https://postgr.es/m/20190725000636.666m5mad25wfbrri%40alap3.anarazel.de
---
 doc/src/sgml/func.sgml                   | 112 ++++++++-
 src/backend/utils/adt/txid.c             | 288 +++++++++--------------
 src/include/access/transam.h             |   3 +
 src/include/catalog/catversion.h         |   2 +-
 src/include/catalog/pg_proc.dat          |  41 ++++
 src/include/catalog/pg_type.dat          |   5 +
 src/test/regress/expected/opr_sanity.out |  11 +-
 src/test/regress/expected/txid.out       |  10 +-
 8 files changed, 277 insertions(+), 195 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 895b4b7b1b..5f7bd28e6d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18916,6 +18916,38 @@ SELECT collation for ('foo' COLLATE "de_DE");
     are stored globally as well.
    </para>
 
+   <indexterm>
+    <primary>xid8_current</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>xid8_current_if_assigned</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>xid8_current_snapshot</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>xid8_snapshot_xip</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>xid8_snapshot_xmax</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>xid8_snapshot_xmin</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>xid8_visible_in_snapshot</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>xid8_status</primary>
+   </indexterm>
+
    <indexterm>
     <primary>txid_current</primary>
    </indexterm>
@@ -18949,12 +18981,74 @@ SELECT collation for ('foo' COLLATE "de_DE");
    </indexterm>
 
    <para>
-    The functions shown in <xref linkend="functions-txid-snapshot"/>
+    The functions shown in <xref linkend="functions-xid8-snapshot"/>
     provide server transaction information in an exportable form.  The main
     use of these functions is to determine which transactions were committed
     between two snapshots.
    </para>
 
+   <table id="functions-xid8-snapshot">
+    <title>Transaction IDs and Snapshots</title>
+    <tgroup cols="3">
+     <thead>
+      <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry></row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry><literal><function>xid8_current()</function></literal></entry>
+       <entry><type>xid8</type></entry>
+       <entry>get current transaction ID, assigning a new one if the current transaction does not have one</entry>
+      </row>
+      <row>
+       <entry><literal><function>xid8_current_if_assigned()</function></literal></entry>
+       <entry><type>xid8</type></entry>
+       <entry>same as <function>xid8_current()</function> but returns null instead of assigning a new transaction ID if none is already assigned</entry>
+      </row>
+      <row>
+       <entry><literal><function>xid8_current_snapshot()</function></literal></entry>
+       <entry><type>xid8_snapshot</type></entry>
+       <entry>get current snapshot</entry>
+      </row>
+      <row>
+       <entry><literal><function>xid8_snapshot_xip(<parameter>xid8_snapshot</parameter>)</function></literal></entry>
+       <entry><type>setof bigint</type></entry>
+       <entry>get in-progress transaction IDs in snapshot</entry>
+      </row>
+      <row>
+       <entry><literal><function>xid8_snapshot_xmax(<parameter>xid8_snapshot</parameter>)</function></literal></entry>
+       <entry><type>xid8</type></entry>
+       <entry>get <literal>xmax</literal> of snapshot</entry>
+      </row>
+      <row>
+       <entry><literal><function>xid8_snapshot_xmin(<parameter>xid8_snapshot</parameter>)</function></literal></entry>
+       <entry><type>xid8</type></entry>
+       <entry>get <literal>xmin</literal> of snapshot</entry>
+      </row>
+      <row>
+       <entry><literal><function>xid8_visible_in_snapshot(<parameter>xid8</parameter>, <parameter>xid8_snapshot</parameter>)</function></literal></entry>
+       <entry><type>boolean</type></entry>
+       <entry>is transaction ID visible in snapshot? (do not use with subtransaction ids)</entry>
+      </row>
+      <row>
+       <entry><literal><function>xid8_status(<parameter>xid8</parameter>)</function></literal></entry>
+       <entry><type>text</type></entry>
+       <entry>report the status of the given transaction: <literal>committed</literal>, <literal>aborted</literal>, <literal>in progress</literal>, or null if the transaction ID is too old</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
+   <para>
+    In releases of <productname>PostgreSQL</productname> before 13 there was
+    no <type>xid8</type> type, so variants of these functions were provided
+    that used <type>bigint</type>.  The older functions with
+    <literal>txid</literal>
+    in the name are still supported for backwards compatibility, but may be
+    removed from a future release.  The <type>bigint</type> variants are shown
+    in <xref linkend="functions-txid-snapshot"/>.
+   </para>
+
    <table id="functions-txid-snapshot">
     <title>Transaction IDs and Snapshots</title>
     <tgroup cols="3">
@@ -18966,42 +19060,42 @@ SELECT collation for ('foo' COLLATE "de_DE");
       <row>
        <entry><literal><function>txid_current()</function></literal></entry>
        <entry><type>bigint</type></entry>
-       <entry>get current transaction ID, assigning a new one if the current transaction does not have one</entry>
+       <entry>see <function>xid8_current()</function></entry>
       </row>
       <row>
        <entry><literal><function>txid_current_if_assigned()</function></literal></entry>
        <entry><type>bigint</type></entry>
-       <entry>same as <function>txid_current()</function> but returns null instead of assigning a new transaction ID if none is already assigned</entry>
+       <entry>see <function>xid8_current_if_assigned()</function></entry>
       </row>
       <row>
        <entry><literal><function>txid_current_snapshot()</function></literal></entry>
        <entry><type>txid_snapshot</type></entry>
-       <entry>get current snapshot</entry>
+       <entry>see <function>xid8_snapshot()</function></entry>
       </row>
       <row>
        <entry><literal><function>txid_snapshot_xip(<parameter>txid_snapshot</parameter>)</function></literal></entry>
        <entry><type>setof bigint</type></entry>
-       <entry>get in-progress transaction IDs in snapshot</entry>
+       <entry>see <function>xid8_snapshot_xip()</function></entry>
       </row>
       <row>
        <entry><literal><function>txid_snapshot_xmax(<parameter>txid_snapshot</parameter>)</function></literal></entry>
        <entry><type>bigint</type></entry>
-       <entry>get <literal>xmax</literal> of snapshot</entry>
+       <entry>see <function>xid8_snapshot_xmax()</function></entry>
       </row>
       <row>
        <entry><literal><function>txid_snapshot_xmin(<parameter>txid_snapshot</parameter>)</function></literal></entry>
        <entry><type>bigint</type></entry>
-       <entry>get <literal>xmin</literal> of snapshot</entry>
+       <entry>see <function>xid8_snapshot_xmin()</function></entry>
       </row>
       <row>
        <entry><literal><function>txid_visible_in_snapshot(<parameter>bigint</parameter>, <parameter>txid_snapshot</parameter>)</function></literal></entry>
        <entry><type>boolean</type></entry>
-       <entry>is transaction ID visible in snapshot? (do not use with subtransaction ids)</entry>
+       <entry>see <function>xid8_visible_in_snapshot()</function></entry>
       </row>
       <row>
        <entry><literal><function>txid_status(<parameter>bigint</parameter>)</function></literal></entry>
        <entry><type>text</type></entry>
-       <entry>report the status of the given transaction: <literal>committed</literal>, <literal>aborted</literal>, <literal>in progress</literal>, or null if the transaction ID is too old</entry>
+       <entry>see <function>xid8_status()</function></entry>
       </row>
      </tbody>
     </tgroup>
diff --git a/src/backend/utils/adt/txid.c b/src/backend/utils/adt/txid.c
index 33272f8030..e0b29d4456 100644
--- a/src/backend/utils/adt/txid.c
+++ b/src/backend/utils/adt/txid.c
@@ -9,6 +9,12 @@
  * rely on being able to correlate subtransaction IDs with their parents
  * via functions such as SubTransGetTopmostTransaction().
  *
+ * These functions are used to support both txid_XXX and xid8_XXX fmgr
+ * functions, since the only difference between them is whether they
+ * expose xid8 or int8 values to users.  This works because the types
+ * are binary compatible for the first 2^63 transactions.  The txid_XXX
+ * variants should eventually be dropped.
+ *
  *
  *	Copyright (c) 2003-2020, PostgreSQL Global Development Group
  *	Author: Jan Wieck, Afilias USA INC.
@@ -34,15 +40,8 @@
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
+#include "utils/xid8.h"
 
-/* txid will be signed int8 in database, so must limit to 63 bits */
-#define MAX_TXID   ((uint64) PG_INT64_MAX)
-
-/* Use unsigned variant internally */
-typedef uint64 txid;
-
-/* sprintf format code for uint64 */
-#define TXID_FMT UINT64_FORMAT
 
 /*
  * If defined, use bsearch() function for searching for txids in snapshots
@@ -63,39 +62,17 @@ typedef struct
 	 */
 	int32		__varsz;
 
-	uint32		nxip;			/* number of txids in xip array */
-	txid		xmin;
-	txid		xmax;
-	/* in-progress txids, xmin <= xip[i] < xmax: */
-	txid		xip[FLEXIBLE_ARRAY_MEMBER];
+	uint32		nxip;			/* number of fxids in xip array */
+	FullTransactionId xmin;
+	FullTransactionId xmax;
+	/* in-progress fxids, xmin <= xip[i] < xmax: */
+	FullTransactionId xip[FLEXIBLE_ARRAY_MEMBER];
 } TxidSnapshot;
 
 #define TXID_SNAPSHOT_SIZE(nxip) \
-	(offsetof(TxidSnapshot, xip) + sizeof(txid) * (nxip))
+	(offsetof(TxidSnapshot, xip) + sizeof(FullTransactionId) * (nxip))
 #define TXID_SNAPSHOT_MAX_NXIP \
-	((MaxAllocSize - offsetof(TxidSnapshot, xip)) / sizeof(txid))
-
-/*
- * Epoch values from xact.c
- */
-typedef struct
-{
-	TransactionId last_xid;
-	uint32		epoch;
-} TxidEpoch;
-
-
-/*
- * Fetch epoch data from xact.c.
- */
-static void
-load_xid_epoch(TxidEpoch *state)
-{
-	FullTransactionId fullXid = ReadNextFullTransactionId();
-
-	state->last_xid = XidFromFullTransactionId(fullXid);
-	state->epoch = EpochFromFullTransactionId(fullXid);
-}
+	((MaxAllocSize - offsetof(TxidSnapshot, xip)) / sizeof(FullTransactionId))
 
 /*
  * Helper to get a TransactionId from a 64-bit xid with wraparound detection.
@@ -111,10 +88,10 @@ load_xid_epoch(TxidEpoch *state)
  * relating to those XIDs.
  */
 static bool
-TransactionIdInRecentPast(uint64 xid_with_epoch, TransactionId *extracted_xid)
+TransactionIdInRecentPast(FullTransactionId fxid, TransactionId *extracted_xid)
 {
-	uint32		xid_epoch = (uint32) (xid_with_epoch >> 32);
-	TransactionId xid = (TransactionId) xid_with_epoch;
+	uint32		xid_epoch = EpochFromFullTransactionId(fxid);
+	TransactionId xid = XidFromFullTransactionId(fxid);
 	uint32		now_epoch;
 	TransactionId now_epoch_next_xid;
 	FullTransactionId now_fullxid;
@@ -134,11 +111,12 @@ TransactionIdInRecentPast(uint64 xid_with_epoch, TransactionId *extracted_xid)
 		return true;
 
 	/* If the transaction ID is in the future, throw an error. */
-	if (xid_with_epoch >= U64FromFullTransactionId(now_fullxid))
+	if (!FullTransactionIdPrecedes(fxid, now_fullxid))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("transaction ID %s is in the future",
-						psprintf(UINT64_FORMAT, xid_with_epoch))));
+						psprintf(UINT64_FORMAT,
+								 U64FromFullTransactionId(fxid)))));
 
 	/*
 	 * ShmemVariableCache->oldestClogXid is protected by CLogTruncationLock,
@@ -164,41 +142,37 @@ TransactionIdInRecentPast(uint64 xid_with_epoch, TransactionId *extracted_xid)
 }
 
 /*
- * do a TransactionId -> txid conversion for an XID near the given epoch
+ * Do a TransactionId -> fxid conversion for an XID that is known to precede
+ * the given 'next_fxid'.
  */
-static txid
-convert_xid(TransactionId xid, const TxidEpoch *state)
+static FullTransactionId
+convert_xid(TransactionId xid, FullTransactionId next_fxid)
 {
-	uint64		epoch;
+	TransactionId next_xid = XidFromFullTransactionId(next_fxid);
+	uint32 epoch = EpochFromFullTransactionId(next_fxid);
 
 	/* return special xid's as-is */
 	if (!TransactionIdIsNormal(xid))
-		return (txid) xid;
+		return FullTransactionIdFromEpochAndXid(0, xid);
 
-	/* xid can be on either side when near wrap-around */
-	epoch = (uint64) state->epoch;
-	if (xid > state->last_xid &&
-		TransactionIdPrecedes(xid, state->last_xid))
+	if (xid > next_xid)
 		epoch--;
-	else if (xid < state->last_xid &&
-			 TransactionIdFollows(xid, state->last_xid))
-		epoch++;
 
-	return (epoch << 32) | xid;
+	return FullTransactionIdFromEpochAndXid(epoch, xid);
 }
 
 /*
  * txid comparator for qsort/bsearch
  */
 static int
-cmp_txid(const void *aa, const void *bb)
+cmp_fxid(const void *aa, const void *bb)
 {
-	txid		a = *(const txid *) aa;
-	txid		b = *(const txid *) bb;
+	FullTransactionId a = *(const FullTransactionId *) aa;
+	FullTransactionId b = *(const FullTransactionId *) bb;
 
-	if (a < b)
+	if (FullTransactionIdPrecedes(a, b))
 		return -1;
-	if (a > b)
+	if (FullTransactionIdPrecedes(b, a))
 		return 1;
 	return 0;
 }
@@ -215,27 +189,29 @@ sort_snapshot(TxidSnapshot *snap)
 {
 	if (snap->nxip > 1)
 	{
-		qsort(snap->xip, snap->nxip, sizeof(txid), cmp_txid);
-		snap->nxip = qunique(snap->xip, snap->nxip, sizeof(txid), cmp_txid);
+		qsort(snap->xip, snap->nxip, sizeof(FullTransactionId), cmp_fxid);
+		snap->nxip = qunique(snap->xip, snap->nxip, sizeof(FullTransactionId),
+							 cmp_fxid);
 	}
 }
 
 /*
- * check txid visibility.
+ * check fxid visibility.
  */
 static bool
-is_visible_txid(txid value, const TxidSnapshot *snap)
+is_visible_fxid(FullTransactionId value, const TxidSnapshot *snap)
 {
-	if (value < snap->xmin)
+	if (FullTransactionIdPrecedes(value, snap->xmin))
 		return true;
-	else if (value >= snap->xmax)
+	else if (!FullTransactionIdPrecedes(value, snap->xmax))
 		return false;
 #ifdef USE_BSEARCH_IF_NXIP_GREATER
 	else if (snap->nxip > USE_BSEARCH_IF_NXIP_GREATER)
 	{
 		void	   *res;
 
-		res = bsearch(&value, snap->xip, snap->nxip, sizeof(txid), cmp_txid);
+		res = bsearch(&value, snap->xip, snap->nxip, sizeof(FullTransactionId),
+					  cmp_fxid);
 		/* if found, transaction is still in progress */
 		return (res) ? false : true;
 	}
@@ -246,7 +222,7 @@ is_visible_txid(txid value, const TxidSnapshot *snap)
 
 		for (i = 0; i < snap->nxip; i++)
 		{
-			if (value == snap->xip[i])
+			if (FullTransactionIdEquals(value, snap->xip[i]))
 				return false;
 		}
 		return true;
@@ -258,7 +234,7 @@ is_visible_txid(txid value, const TxidSnapshot *snap)
  */
 
 static StringInfo
-buf_init(txid xmin, txid xmax)
+buf_init(FullTransactionId xmin, FullTransactionId xmax)
 {
 	TxidSnapshot snap;
 	StringInfo	buf;
@@ -273,14 +249,14 @@ buf_init(txid xmin, txid xmax)
 }
 
 static void
-buf_add_txid(StringInfo buf, txid xid)
+buf_add_txid(StringInfo buf, FullTransactionId fxid)
 {
 	TxidSnapshot *snap = (TxidSnapshot *) buf->data;
 
 	/* do this before possible realloc */
 	snap->nxip++;
 
-	appendBinaryStringInfo(buf, (char *) &xid, sizeof(xid));
+	appendBinaryStringInfo(buf, (char *) &fxid, sizeof(fxid));
 }
 
 static TxidSnapshot *
@@ -297,68 +273,34 @@ buf_finalize(StringInfo buf)
 	return snap;
 }
 
-/*
- * simple number parser.
- *
- * We return 0 on error, which is invalid value for txid.
- */
-static txid
-str2txid(const char *s, const char **endp)
-{
-	txid		val = 0;
-	txid		cutoff = MAX_TXID / 10;
-	txid		cutlim = MAX_TXID % 10;
-
-	for (; *s; s++)
-	{
-		unsigned	d;
-
-		if (*s < '0' || *s > '9')
-			break;
-		d = *s - '0';
-
-		/*
-		 * check for overflow
-		 */
-		if (val > cutoff || (val == cutoff && d > cutlim))
-		{
-			val = 0;
-			break;
-		}
-
-		val = val * 10 + d;
-	}
-	if (endp)
-		*endp = s;
-	return val;
-}
-
 /*
  * parse snapshot from cstring
  */
 static TxidSnapshot *
 parse_snapshot(const char *str)
 {
-	txid		xmin;
-	txid		xmax;
-	txid		last_val = 0,
-				val;
+	FullTransactionId xmin;
+	FullTransactionId xmax;
+	FullTransactionId last_val = InvalidFullTransactionId;
+	FullTransactionId val;
 	const char *str_start = str;
-	const char *endp;
+	char *endp;
 	StringInfo	buf;
 
-	xmin = str2txid(str, &endp);
+	xmin = FullTransactionIdFromU64(pg_strtouint64(str, &endp, 10));
 	if (*endp != ':')
 		goto bad_format;
 	str = endp + 1;
 
-	xmax = str2txid(str, &endp);
+	xmax = FullTransactionIdFromU64(pg_strtouint64(str, &endp, 10));
 	if (*endp != ':')
 		goto bad_format;
 	str = endp + 1;
 
 	/* it should look sane */
-	if (xmin == 0 || xmax == 0 || xmin > xmax)
+	if (!FullTransactionIdIsValid(xmin) ||
+		!FullTransactionIdIsValid(xmax) ||
+		FullTransactionIdPrecedes(xmax, xmin))
 		goto bad_format;
 
 	/* allocate buffer */
@@ -368,15 +310,17 @@ parse_snapshot(const char *str)
 	while (*str != '\0')
 	{
 		/* read next value */
-		val = str2txid(str, &endp);
+		val = FullTransactionIdFromU64(pg_strtouint64(str, &endp, 10));
 		str = endp;
 
 		/* require the input to be in order */
-		if (val < xmin || val >= xmax || val < last_val)
+		if (FullTransactionIdPrecedes(val, xmin) ||
+			FullTransactionIdFollowsOrEquals(val, xmax) ||
+			FullTransactionIdPrecedes(val, last_val))
 			goto bad_format;
 
 		/* skip duplicates */
-		if (val != last_val)
+		if (!FullTransactionIdEquals(val, last_val))
 			buf_add_txid(buf, val);
 		last_val = val;
 
@@ -392,7 +336,7 @@ bad_format:
 	ereport(ERROR,
 			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 			 errmsg("invalid input syntax for type %s: \"%s\"",
-					"txid_snapshot", str_start)));
+					"xid8_snapshot", str_start)));
 	return NULL;				/* keep compiler quiet */
 }
 
@@ -405,33 +349,23 @@ bad_format:
  */
 
 /*
- * txid_current() returns int8
+ * txid_current() returns xid8
  *
- *	Return the current toplevel transaction ID as TXID
+ *	Return the current toplevel full transaction ID.
  *	If the current transaction does not have one, one is assigned.
- *
- *	This value has the epoch as the high 32 bits and the 32-bit xid
- *	as the low 32 bits.
  */
 Datum
 txid_current(PG_FUNCTION_ARGS)
 {
-	txid		val;
-	TxidEpoch	state;
-
 	/*
 	 * Must prevent during recovery because if an xid is not assigned we try
 	 * to assign one, which would fail. Programs already rely on this function
 	 * to always return a valid current xid, so we should not change this to
 	 * return NULL or similar invalid xid.
 	 */
-	PreventCommandDuringRecovery("txid_current()");
-
-	load_xid_epoch(&state);
-
-	val = convert_xid(GetTopTransactionId(), &state);
+	PreventCommandDuringRecovery("xid8_current()");
 
-	PG_RETURN_INT64(val);
+	PG_RETURN_FULLTRANSACTIONID(GetTopFullTransactionId());
 }
 
 /*
@@ -441,18 +375,12 @@ txid_current(PG_FUNCTION_ARGS)
 Datum
 txid_current_if_assigned(PG_FUNCTION_ARGS)
 {
-	txid		val;
-	TxidEpoch	state;
-	TransactionId topxid = GetTopTransactionIdIfAny();
+	FullTransactionId topfxid = GetTopFullTransactionIdIfAny();
 
-	if (topxid == InvalidTransactionId)
+	if (!FullTransactionIdIsValid(topfxid))
 		PG_RETURN_NULL();
 
-	load_xid_epoch(&state);
-
-	val = convert_xid(topxid, &state);
-
-	PG_RETURN_INT64(val);
+	PG_RETURN_FULLTRANSACTIONID(topfxid);
 }
 
 /*
@@ -468,15 +396,13 @@ txid_current_snapshot(PG_FUNCTION_ARGS)
 	TxidSnapshot *snap;
 	uint32		nxip,
 				i;
-	TxidEpoch	state;
 	Snapshot	cur;
+	FullTransactionId next_fxid = ReadNextFullTransactionId();
 
 	cur = GetActiveSnapshot();
 	if (cur == NULL)
 		elog(ERROR, "no active snapshot set");
 
-	load_xid_epoch(&state);
-
 	/*
 	 * Compile-time limits on the procarray (MAX_BACKENDS processes plus
 	 * MAX_BACKENDS prepared transactions) guarantee nxip won't be too large.
@@ -489,11 +415,11 @@ txid_current_snapshot(PG_FUNCTION_ARGS)
 	snap = palloc(TXID_SNAPSHOT_SIZE(nxip));
 
 	/* fill */
-	snap->xmin = convert_xid(cur->xmin, &state);
-	snap->xmax = convert_xid(cur->xmax, &state);
+	snap->xmin = convert_xid(cur->xmin, next_fxid);
+	snap->xmax = convert_xid(cur->xmax, next_fxid);
 	snap->nxip = nxip;
 	for (i = 0; i < nxip; i++)
-		snap->xip[i] = convert_xid(cur->xip[i], &state);
+		snap->xip[i] = convert_xid(cur->xip[i], next_fxid);
 
 	/*
 	 * We want them guaranteed to be in ascending order.  This also removes
@@ -540,14 +466,17 @@ txid_snapshot_out(PG_FUNCTION_ARGS)
 
 	initStringInfo(&str);
 
-	appendStringInfo(&str, TXID_FMT ":", snap->xmin);
-	appendStringInfo(&str, TXID_FMT ":", snap->xmax);
+	appendStringInfo(&str, UINT64_FORMAT ":",
+					 U64FromFullTransactionId(snap->xmin));
+	appendStringInfo(&str, UINT64_FORMAT ":",
+					 U64FromFullTransactionId(snap->xmax));
 
 	for (i = 0; i < snap->nxip; i++)
 	{
 		if (i > 0)
 			appendStringInfoChar(&str, ',');
-		appendStringInfo(&str, TXID_FMT, snap->xip[i]);
+		appendStringInfo(&str, UINT64_FORMAT,
+						 U64FromFullTransactionId(snap->xip[i]));
 	}
 
 	PG_RETURN_CSTRING(str.data);
@@ -565,20 +494,22 @@ txid_snapshot_recv(PG_FUNCTION_ARGS)
 {
 	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
 	TxidSnapshot *snap;
-	txid		last = 0;
+	FullTransactionId last = InvalidFullTransactionId;
 	int			nxip;
 	int			i;
-	txid		xmin,
-				xmax;
+	FullTransactionId xmin;
+	FullTransactionId xmax;
 
 	/* load and validate nxip */
 	nxip = pq_getmsgint(buf, 4);
 	if (nxip < 0 || nxip > TXID_SNAPSHOT_MAX_NXIP)
 		goto bad_format;
 
-	xmin = pq_getmsgint64(buf);
-	xmax = pq_getmsgint64(buf);
-	if (xmin == 0 || xmax == 0 || xmin > xmax || xmax > MAX_TXID)
+	xmin = FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
+	xmax = FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
+	if (!FullTransactionIdIsValid(xmin) ||
+		!FullTransactionIdIsValid(xmax) ||
+		FullTransactionIdPrecedes(xmax, xmin))
 		goto bad_format;
 
 	snap = palloc(TXID_SNAPSHOT_SIZE(nxip));
@@ -587,13 +518,16 @@ txid_snapshot_recv(PG_FUNCTION_ARGS)
 
 	for (i = 0; i < nxip; i++)
 	{
-		txid		cur = pq_getmsgint64(buf);
+		FullTransactionId cur =
+			FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
 
-		if (cur < last || cur < xmin || cur >= xmax)
+		if (FullTransactionIdPrecedes(cur, last) ||
+			FullTransactionIdPrecedes(cur, xmin) ||
+			FullTransactionIdPrecedes(xmax, cur))
 			goto bad_format;
 
 		/* skip duplicate xips */
-		if (cur == last)
+		if (FullTransactionIdEquals(cur, last))
 		{
 			i--;
 			nxip--;
@@ -610,7 +544,7 @@ txid_snapshot_recv(PG_FUNCTION_ARGS)
 bad_format:
 	ereport(ERROR,
 			(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
-			 errmsg("invalid external txid_snapshot data")));
+			 errmsg("invalid external xid8_snapshot data")));
 	PG_RETURN_POINTER(NULL);	/* keep compiler quiet */
 }
 
@@ -619,7 +553,7 @@ bad_format:
  *
  *		binary output function for type txid_snapshot
  *
- *		format: int4 nxip, int8 xmin, int8 xmax, int8 xip
+ *		format: int4 nxip, u64 xmin, u64 xmax, u64 xip...
  */
 Datum
 txid_snapshot_send(PG_FUNCTION_ARGS)
@@ -630,29 +564,29 @@ txid_snapshot_send(PG_FUNCTION_ARGS)
 
 	pq_begintypsend(&buf);
 	pq_sendint32(&buf, snap->nxip);
-	pq_sendint64(&buf, snap->xmin);
-	pq_sendint64(&buf, snap->xmax);
+	pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xmin));
+	pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xmax));
 	for (i = 0; i < snap->nxip; i++)
-		pq_sendint64(&buf, snap->xip[i]);
+		pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xip[i]));
 	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
 }
 
 /*
- * txid_visible_in_snapshot(int8, txid_snapshot) returns bool
+ * txid_visible_in_snapshot(xid8, txid_snapshot) returns bool
  *
  *		is txid visible in snapshot ?
  */
 Datum
 txid_visible_in_snapshot(PG_FUNCTION_ARGS)
 {
-	txid		value = PG_GETARG_INT64(0);
+	FullTransactionId value = PG_GETARG_FULLTRANSACTIONID(0);
 	TxidSnapshot *snap = (TxidSnapshot *) PG_GETARG_VARLENA_P(1);
 
-	PG_RETURN_BOOL(is_visible_txid(value, snap));
+	PG_RETURN_BOOL(is_visible_fxid(value, snap));
 }
 
 /*
- * txid_snapshot_xmin(txid_snapshot) returns int8
+ * txid_snapshot_xmin(txid_snapshot) returns xid8
  *
  *		return snapshot's xmin
  */
@@ -661,11 +595,11 @@ txid_snapshot_xmin(PG_FUNCTION_ARGS)
 {
 	TxidSnapshot *snap = (TxidSnapshot *) PG_GETARG_VARLENA_P(0);
 
-	PG_RETURN_INT64(snap->xmin);
+	PG_RETURN_FULLTRANSACTIONID(snap->xmin);
 }
 
 /*
- * txid_snapshot_xmax(txid_snapshot) returns int8
+ * txid_snapshot_xmax(txid_snapshot) returns xid8
  *
  *		return snapshot's xmax
  */
@@ -674,11 +608,11 @@ txid_snapshot_xmax(PG_FUNCTION_ARGS)
 {
 	TxidSnapshot *snap = (TxidSnapshot *) PG_GETARG_VARLENA_P(0);
 
-	PG_RETURN_INT64(snap->xmax);
+	PG_RETURN_FULLTRANSACTIONID(snap->xmax);
 }
 
 /*
- * txid_snapshot_xip(txid_snapshot) returns setof int8
+ * txid_snapshot_xip(txid_snapshot) returns setof xid8
  *
  *		return in-progress TXIDs in snapshot.
  */
@@ -687,7 +621,7 @@ txid_snapshot_xip(PG_FUNCTION_ARGS)
 {
 	FuncCallContext *fctx;
 	TxidSnapshot *snap;
-	txid		value;
+	FullTransactionId value;
 
 	/* on first call initialize fctx and get copy of snapshot */
 	if (SRF_IS_FIRSTCALL())
@@ -709,7 +643,7 @@ txid_snapshot_xip(PG_FUNCTION_ARGS)
 	if (fctx->call_cntr < snap->nxip)
 	{
 		value = snap->xip[fctx->call_cntr];
-		SRF_RETURN_NEXT(fctx, Int64GetDatum(value));
+		SRF_RETURN_NEXT(fctx, FullTransactionIdGetDatum(value));
 	}
 	else
 	{
@@ -731,7 +665,7 @@ Datum
 txid_status(PG_FUNCTION_ARGS)
 {
 	const char *status;
-	uint64		xid_with_epoch = PG_GETARG_INT64(0);
+	FullTransactionId fxid = PG_GETARG_FULLTRANSACTIONID(0);
 	TransactionId xid;
 
 	/*
@@ -739,7 +673,7 @@ txid_status(PG_FUNCTION_ARGS)
 	 * an I/O error on SLRU lookup.
 	 */
 	LWLockAcquire(CLogTruncationLock, LW_SHARED);
-	if (TransactionIdInRecentPast(xid_with_epoch, &xid))
+	if (TransactionIdInRecentPast(fxid, &xid))
 	{
 		Assert(TransactionIdIsValid(xid));
 
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 4dc9a0c67b..9a808f64eb 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -49,6 +49,9 @@
 #define U64FromFullTransactionId(x)		((x).value)
 #define FullTransactionIdEquals(a, b)	((a).value == (b).value)
 #define FullTransactionIdPrecedes(a, b)	((a).value < (b).value)
+#define FullTransactionIdPrecedesOrEquals(a, b) ((a).value <= (b).value)
+#define FullTransactionIdFollows(a, b) ((a).value > (b).value)
+#define FullTransactionIdFollowsOrEquals(a, b) ((a).value >= (b).value)
 #define FullTransactionIdIsValid(x)		TransactionIdIsValid(XidFromFullTransactionId(x))
 #define InvalidFullTransactionId		FullTransactionIdFromEpochAndXid(0, InvalidTransactionId)
 
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index efd5df5517..ea3fdbe393 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	202001271
+#define CATALOG_VERSION_NO	202001272
 
 #endif
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 95a888e060..f941739ad5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9460,6 +9460,47 @@
   proname => 'txid_status', provolatile => 'v', prorettype => 'text',
   proargtypes => 'int8', prosrc => 'txid_status' },
 
+# xid8-based variants of txid functions
+{ oid => '9247', descr => 'I/O',
+  proname => 'xid8_snapshot_in', prorettype => 'xid8_snapshot',
+  proargtypes => 'cstring', prosrc => 'txid_snapshot_in' },
+{ oid => '9248', descr => 'I/O',
+  proname => 'xid8_snapshot_out', prorettype => 'cstring',
+  proargtypes => 'xid8_snapshot', prosrc => 'txid_snapshot_out' },
+{ oid => '9249', descr => 'I/O',
+  proname => 'xid8_snapshot_recv', prorettype => 'xid8_snapshot',
+  proargtypes => 'internal', prosrc => 'txid_snapshot_recv' },
+{ oid => '9250', descr => 'I/O',
+  proname => 'xid8_snapshot_send', prorettype => 'bytea',
+  proargtypes => 'xid8_snapshot', prosrc => 'txid_snapshot_send' },
+{ oid => '9251', descr => 'get current transaction ID',
+  proname => 'xid8_current', provolatile => 's', proparallel => 'u',
+  prorettype => 'xid8', proargtypes => '', prosrc => 'txid_current' },
+{ oid => '9252', descr => 'get current transaction ID',
+  proname => 'xid8_current_if_assigned', provolatile => 's', proparallel => 'u',
+  prorettype => 'xid8', proargtypes => '',
+  prosrc => 'txid_current_if_assigned' },
+{ oid => '9253', descr => 'get current snapshot',
+  proname => 'xid8_current_snapshot', provolatile => 's',
+  prorettype => 'xid8_snapshot', proargtypes => '',
+  prosrc => 'txid_current_snapshot' },
+{ oid => '9254', descr => 'get xmin of snapshot',
+  proname => 'xid8_snapshot_xmin', prorettype => 'xid8',
+  proargtypes => 'xid8_snapshot', prosrc => 'txid_snapshot_xmin' },
+{ oid => '9255', descr => 'get xmax of snapshot',
+  proname => 'xid8_snapshot_xmax', prorettype => 'xid8',
+  proargtypes => 'xid8_snapshot', prosrc => 'txid_snapshot_xmax' },
+{ oid => '9256', descr => 'get set of in-progress transactions in snapshot',
+  proname => 'xid8_snapshot_xip', prorows => '50', proretset => 't',
+  prorettype => 'xid8', proargtypes => 'xid8_snapshot',
+  prosrc => 'txid_snapshot_xip' },
+{ oid => '9257', descr => 'is xid8 visible in snapshot?',
+  proname => 'xid8_visible_in_snapshot', prorettype => 'bool',
+  proargtypes => 'xid8 xid8_snapshot', prosrc => 'txid_visible_in_snapshot' },
+{ oid => '9258', descr => 'commit status of transaction',
+  proname => 'xid8_status', provolatile => 'v', prorettype => 'text',
+  proargtypes => 'xid8', prosrc => 'txid_status' },
+
 # record comparison using normal comparison rules
 { oid => '2981',
   proname => 'record_eq', prorettype => 'bool', proargtypes => 'record record',
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index 89d9c40e42..7ae5eabd21 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -455,6 +455,11 @@
   typcategory => 'U', typinput => 'txid_snapshot_in',
   typoutput => 'txid_snapshot_out', typreceive => 'txid_snapshot_recv',
   typsend => 'txid_snapshot_send', typalign => 'd', typstorage => 'x' },
+{ oid => '8355', array_type_oid => '8356', descr => 'xid8 snapshot',
+  typname => 'xid8_snapshot', typlen => '-1', typbyval => 'f',
+  typcategory => 'U', typinput => 'xid8_snapshot_in',
+  typoutput => 'xid8_snapshot_out', typreceive => 'xid8_snapshot_recv',
+  typsend => 'xid8_snapshot_send', typalign => 'd', typstorage => 'x' },
 
 # range types
 { oid => '3904', array_type_oid => '3905', descr => 'range of integers',
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index dcaf31eb7b..246419ca1a 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -194,9 +194,11 @@ WHERE p1.oid != p2.oid AND
 ORDER BY 1, 2;
  prorettype | prorettype 
 ------------+------------
+         20 |       9560
          25 |       1043
        1114 |       1184
-(2 rows)
+       2970 |       8355
+(4 rows)
 
 SELECT DISTINCT p1.proargtypes[0], p2.proargtypes[0]
 FROM pg_proc AS p1, pg_proc AS p2
@@ -210,11 +212,13 @@ WHERE p1.oid != p2.oid AND
 ORDER BY 1, 2;
  proargtypes | proargtypes 
 -------------+-------------
+          20 |        9560
           25 |        1042
           25 |        1043
         1114 |        1184
         1560 |        1562
-(4 rows)
+        2970 |        8355
+(6 rows)
 
 SELECT DISTINCT p1.proargtypes[1], p2.proargtypes[1]
 FROM pg_proc AS p1, pg_proc AS p2
@@ -231,7 +235,8 @@ ORDER BY 1, 2;
           23 |          28
         1114 |        1184
         1560 |        1562
-(3 rows)
+        2970 |        8355
+(4 rows)
 
 SELECT DISTINCT p1.proargtypes[2], p2.proargtypes[2]
 FROM pg_proc AS p1, pg_proc AS p2
diff --git a/src/test/regress/expected/txid.out b/src/test/regress/expected/txid.out
index 015dae3051..69867dbf16 100644
--- a/src/test/regress/expected/txid.out
+++ b/src/test/regress/expected/txid.out
@@ -20,19 +20,19 @@ select '12:16:14,14'::txid_snapshot;
 
 -- errors
 select '31:12:'::txid_snapshot;
-ERROR:  invalid input syntax for type txid_snapshot: "31:12:"
+ERROR:  invalid input syntax for type xid8_snapshot: "31:12:"
 LINE 1: select '31:12:'::txid_snapshot;
                ^
 select '0:1:'::txid_snapshot;
-ERROR:  invalid input syntax for type txid_snapshot: "0:1:"
+ERROR:  invalid input syntax for type xid8_snapshot: "0:1:"
 LINE 1: select '0:1:'::txid_snapshot;
                ^
 select '12:13:0'::txid_snapshot;
-ERROR:  invalid input syntax for type txid_snapshot: "12:13:0"
+ERROR:  invalid input syntax for type xid8_snapshot: "12:13:0"
 LINE 1: select '12:13:0'::txid_snapshot;
                ^
 select '12:16:14,13'::txid_snapshot;
-ERROR:  invalid input syntax for type txid_snapshot: "12:16:14,13"
+ERROR:  invalid input syntax for type xid8_snapshot: "12:16:14,13"
 LINE 1: select '12:16:14,13'::txid_snapshot;
                ^
 create temp table snapshot_test (
@@ -235,7 +235,7 @@ SELECT txid_snapshot '1:9223372036854775807:3';
 (1 row)
 
 SELECT txid_snapshot '1:9223372036854775808:3';
-ERROR:  invalid input syntax for type txid_snapshot: "1:9223372036854775808:3"
+ERROR:  invalid input syntax for type xid8_snapshot: "1:9223372036854775808:3"
 LINE 1: SELECT txid_snapshot '1:9223372036854775808:3';
                              ^
 -- test txid_current_if_assigned
-- 
2.23.0

