On Sat, Mar 21, 2020 at 11:14 AM Thomas Munro <thomas.mu...@gmail.com> wrote:
> * updated OIDs to avoid collisions
> * added btequalimage to btree/xid8_ops

Here's the version I'm planning to commit tomorrow, if no one objects.  Changes:

* txid.c renamed to xid8funcs.c
* remaining traces of "txid" replaced various internal identifiers
* s/backwards compatible/backward compatible/ in funcs.sgml (en_GB -> en_US)
From 308b094306f587a6736963983162290ac2808ac7 Mon Sep 17 00:00:00 2001
From: Thomas Munro <tmu...@postgresql.org>
Date: Thu, 2 Apr 2020 15:00:23 +1300
Subject: [PATCH v8 1/2] Add SQL type xid8 to expose FullTransactionId to
 users.

Similar to xid, but 64 bits wide.  This new type is suitable for use in
various system views and administration functions.

Reviewed-by: Fujii Masao <masao.fu...@oss.nttdata.com>
Reviewed-by: Takao Fujii <btfujii...@oss.nttdata.com>
Reviewed-by: Yoshikazu Imai <imai.yoshik...@fujitsu.com>
Reviewed-by: Mark Dilger <hornschnor...@gmail.com>
Discussion: https://postgr.es/m/20190725000636.666m5mad25wfbrri%40alap3.anarazel.de
---
 doc/src/sgml/datatype.sgml               |   7 ++
 src/backend/access/hash/hashvalidate.c   |   3 +
 src/backend/utils/adt/xid.c              | 116 +++++++++++++++++++
 src/fe_utils/print.c                     |   1 +
 src/include/access/transam.h             |  14 +++
 src/include/catalog/pg_amop.dat          |  22 ++++
 src/include/catalog/pg_amproc.dat        |   8 ++
 src/include/catalog/pg_cast.dat          |   4 +
 src/include/catalog/pg_opclass.dat       |   4 +
 src/include/catalog/pg_operator.dat      |  25 +++++
 src/include/catalog/pg_opfamily.dat      |   4 +
 src/include/catalog/pg_proc.dat          |  36 ++++++
 src/include/catalog/pg_type.dat          |   4 +
 src/include/utils/xid8.h                 |  22 ++++
 src/test/regress/expected/opr_sanity.out |   7 ++
 src/test/regress/expected/xid.out        | 136 +++++++++++++++++++++++
 src/test/regress/parallel_schedule       |   2 +-
 src/test/regress/serial_schedule         |   1 +
 src/test/regress/sql/xid.sql             |  48 ++++++++
 19 files changed, 463 insertions(+), 1 deletion(-)
 create mode 100644 src/include/utils/xid8.h
 create mode 100644 src/test/regress/expected/xid.out
 create mode 100644 src/test/regress/sql/xid.sql

diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 03971822c2..89f3a7c119 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4516,6 +4516,10 @@ INSERT INTO mytable VALUES(-1);  -- fails
     <primary>regtype</primary>
    </indexterm>
 
+   <indexterm zone="datatype-oid">
+    <primary>xid8</primary>
+   </indexterm>
+
    <indexterm zone="datatype-oid">
     <primary>cid</primary>
    </indexterm>
@@ -4719,6 +4723,9 @@ SELECT * FROM pg_attribute
     Another identifier type used by the system is <type>xid</type>, or transaction
     (abbreviated <abbrev>xact</abbrev>) identifier.  This is the data type of the system columns
     <structfield>xmin</structfield> and <structfield>xmax</structfield>.  Transaction identifiers are 32-bit quantities.
+    In some contexts, a 64-bit variant <type>xid8</type> is used.  Unlike
+    <type>xid</type> values, <type>xid8</type> values increase strictly
+    monotonically and cannot be reused in the lifetime of a database cluster.
    </para>
 
    <para>
diff --git a/src/backend/access/hash/hashvalidate.c b/src/backend/access/hash/hashvalidate.c
index 7b08ed5354..b3d1367fec 100644
--- a/src/backend/access/hash/hashvalidate.c
+++ b/src/backend/access/hash/hashvalidate.c
@@ -317,6 +317,9 @@ check_hash_func_signature(Oid funcid, int16 amprocnum, Oid argtype)
 			(argtype == DATEOID ||
 			 argtype == XIDOID || argtype == CIDOID))
 			 /* okay, allowed use of hashint4() */ ;
+		else if ((funcid == F_HASHINT8 || funcid == F_HASHINT8EXTENDED) &&
+			(argtype == XID8OID))
+			 /* okay, allowed use of hashint8() */ ;
 		else if ((funcid == F_TIMESTAMP_HASH ||
 				  funcid == F_TIMESTAMP_HASH_EXTENDED) &&
 				 argtype == TIMESTAMPTZOID)
diff --git a/src/backend/utils/adt/xid.c b/src/backend/utils/adt/xid.c
index db6fc9dd6b..20389aff1d 100644
--- a/src/backend/utils/adt/xid.c
+++ b/src/backend/utils/adt/xid.c
@@ -21,6 +21,7 @@
 #include "access/xact.h"
 #include "libpq/pqformat.h"
 #include "utils/builtins.h"
+#include "utils/xid8.h"
 
 #define PG_GETARG_TRANSACTIONID(n)	DatumGetTransactionId(PG_GETARG_DATUM(n))
 #define PG_RETURN_TRANSACTIONID(x)	return TransactionIdGetDatum(x)
@@ -147,6 +148,121 @@ xidComparator(const void *arg1, const void *arg2)
 	return 0;
 }
 
+Datum
+xid8toxid(PG_FUNCTION_ARGS)
+{
+	FullTransactionId fxid = PG_GETARG_FULLTRANSACTIONID(0);
+
+	PG_RETURN_TRANSACTIONID(XidFromFullTransactionId(fxid));
+}
+
+Datum
+xid8in(PG_FUNCTION_ARGS)
+{
+	char	   *str = PG_GETARG_CSTRING(0);
+
+	PG_RETURN_FULLTRANSACTIONID(FullTransactionIdFromU64(pg_strtouint64(str, NULL, 0)));
+}
+
+Datum
+xid8out(PG_FUNCTION_ARGS)
+{
+	FullTransactionId fxid = PG_GETARG_FULLTRANSACTIONID(0);
+	char	   *result = (char *) palloc(21);
+
+	snprintf(result, 21, UINT64_FORMAT, U64FromFullTransactionId(fxid));
+	PG_RETURN_CSTRING(result);
+}
+
+Datum
+xid8recv(PG_FUNCTION_ARGS)
+{
+	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
+	uint64		value;
+
+	value = (uint64) pq_getmsgint64(buf);
+	PG_RETURN_FULLTRANSACTIONID(FullTransactionIdFromU64(value));
+}
+
+Datum
+xid8send(PG_FUNCTION_ARGS)
+{
+	FullTransactionId arg1 = PG_GETARG_FULLTRANSACTIONID(0);
+	StringInfoData buf;
+
+	pq_begintypsend(&buf);
+	pq_sendint64(&buf, (uint64) U64FromFullTransactionId(arg1));
+	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+}
+
+Datum
+xid8eq(PG_FUNCTION_ARGS)
+{
+	FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0);
+	FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1);
+
+	PG_RETURN_BOOL(FullTransactionIdEquals(fxid1, fxid2));
+}
+
+Datum
+xid8ne(PG_FUNCTION_ARGS)
+{
+	FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0);
+	FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1);
+
+	PG_RETURN_BOOL(!FullTransactionIdEquals(fxid1, fxid2));
+}
+
+Datum
+xid8lt(PG_FUNCTION_ARGS)
+{
+	FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0);
+	FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1);
+
+	PG_RETURN_BOOL(FullTransactionIdPrecedes(fxid1, fxid2));
+}
+
+Datum
+xid8gt(PG_FUNCTION_ARGS)
+{
+	FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0);
+	FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1);
+
+	PG_RETURN_BOOL(FullTransactionIdFollows(fxid1, fxid2));
+}
+
+Datum
+xid8le(PG_FUNCTION_ARGS)
+{
+	FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0);
+	FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1);
+
+	PG_RETURN_BOOL(FullTransactionIdPrecedesOrEquals(fxid1, fxid2));
+}
+
+Datum
+xid8ge(PG_FUNCTION_ARGS)
+{
+	FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0);
+	FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1);
+
+	PG_RETURN_BOOL(FullTransactionIdFollowsOrEquals(fxid1, fxid2));
+}
+
+Datum
+xid8cmp(PG_FUNCTION_ARGS)
+{
+	FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0);
+	FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1);
+
+	if (FullTransactionIdFollows(fxid1, fxid2))
+		PG_RETURN_INT32(1);
+	else if (FullTransactionIdEquals(fxid1, fxid2))
+		PG_RETURN_INT32(0);
+	else
+		PG_RETURN_INT32(-1);
+}
+
 /*****************************************************************************
  *	 COMMAND IDENTIFIER ROUTINES											 *
  *****************************************************************************/
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 06096cc86c..66a50f183f 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3509,6 +3509,7 @@ column_type_alignment(Oid ftype)
 		case NUMERICOID:
 		case OIDOID:
 		case XIDOID:
+		case XID8OID:
 		case CIDOID:
 		case CASHOID:
 			align = 'r';
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 6a947b958b..9a808f64eb 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -47,7 +47,11 @@
 #define EpochFromFullTransactionId(x)	((uint32) ((x).value >> 32))
 #define XidFromFullTransactionId(x)		((uint32) (x).value)
 #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)
 
@@ -71,6 +75,16 @@ FullTransactionIdFromEpochAndXid(uint32 epoch, TransactionId xid)
 	return result;
 }
 
+static inline FullTransactionId
+FullTransactionIdFromU64(uint64 value)
+{
+	FullTransactionId result;
+
+	result.value = value;
+
+	return result;
+}
+
 /* advance a transaction ID variable, handling wraparound correctly */
 #define TransactionIdAdvance(dest)	\
 	do { \
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 11aaa519c8..b5dfaad9ec 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -180,6 +180,24 @@
 { amopfamily => 'btree/oid_ops', amoplefttype => 'oid', amoprighttype => 'oid',
   amopstrategy => '5', amopopr => '>(oid,oid)', amopmethod => 'btree' },
 
+# btree xid8_ops
+
+{ amopfamily => 'btree/xid8_ops', amoplefttype => 'xid8',
+  amoprighttype => 'xid8', amopstrategy => '1', amopopr => '<(xid8,xid8)',
+  amopmethod => 'btree' },
+{ amopfamily => 'btree/xid8_ops', amoplefttype => 'xid8',
+  amoprighttype => 'xid8', amopstrategy => '2', amopopr => '<=(xid8,xid8)',
+  amopmethod => 'btree' },
+{ amopfamily => 'btree/xid8_ops', amoplefttype => 'xid8',
+  amoprighttype => 'xid8', amopstrategy => '3', amopopr => '=(xid8,xid8)',
+  amopmethod => 'btree' },
+{ amopfamily => 'btree/xid8_ops', amoplefttype => 'xid8',
+  amoprighttype => 'xid8', amopstrategy => '4', amopopr => '>=(xid8,xid8)',
+  amopmethod => 'btree' },
+{ amopfamily => 'btree/xid8_ops', amoplefttype => 'xid8',
+  amoprighttype => 'xid8', amopstrategy => '5', amopopr => '>(xid8,xid8)',
+  amopmethod => 'btree' },
+
 # btree tid_ops
 
 { amopfamily => 'btree/tid_ops', amoplefttype => 'tid', amoprighttype => 'tid',
@@ -1009,6 +1027,10 @@
 { amopfamily => 'hash/xid_ops', amoplefttype => 'xid', amoprighttype => 'xid',
   amopstrategy => '1', amopopr => '=(xid,xid)', amopmethod => 'hash' },
 
+# xid8_ops
+{ amopfamily => 'hash/xid8_ops', amoplefttype => 'xid8', amoprighttype => 'xid8',
+  amopstrategy => '1', amopopr => '=(xid8,xid8)', amopmethod => 'hash' },
+
 # cid_ops
 { amopfamily => 'hash/cid_ops', amoplefttype => 'cid', amoprighttype => 'cid',
   amopstrategy => '1', amopopr => '=(cid,cid)', amopmethod => 'hash' },
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index cef63b2a71..37b580883f 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -287,6 +287,10 @@
   amprocrighttype => 'anyrange', amprocnum => '1', amproc => 'range_cmp' },
 { amprocfamily => 'btree/jsonb_ops', amproclefttype => 'jsonb',
   amprocrighttype => 'jsonb', amprocnum => '1', amproc => 'jsonb_cmp' },
+{ amprocfamily => 'btree/xid8_ops', amproclefttype => 'xid8',
+  amprocrighttype => 'xid8', amprocnum => '1', amproc => 'xid8cmp' },
+{ amprocfamily => 'btree/xid8_ops', amproclefttype => 'xid8',
+  amprocrighttype => 'xid8', amprocnum => '4', amproc => 'btequalimage' },
 
 # hash
 { amprocfamily => 'hash/bpchar_ops', amproclefttype => 'bpchar',
@@ -399,6 +403,10 @@
   amprocrighttype => 'xid', amprocnum => '1', amproc => 'hashint4' },
 { amprocfamily => 'hash/xid_ops', amproclefttype => 'xid',
   amprocrighttype => 'xid', amprocnum => '2', amproc => 'hashint4extended' },
+{ amprocfamily => 'hash/xid8_ops', amproclefttype => 'xid8',
+  amprocrighttype => 'xid8', amprocnum => '1', amproc => 'hashint8' },
+{ amprocfamily => 'hash/xid8_ops', amproclefttype => 'xid8',
+  amprocrighttype => 'xid8', amprocnum => '2', amproc => 'hashint8extended' },
 { amprocfamily => 'hash/cid_ops', amproclefttype => 'cid',
   amprocrighttype => 'cid', amprocnum => '1', amproc => 'hashint4' },
 { amprocfamily => 'hash/cid_ops', amproclefttype => 'cid',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 01c5328ddd..5a58f50fbb 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -93,6 +93,10 @@
 { castsource => 'bool', casttarget => 'int4', castfunc => 'int4(bool)',
   castcontext => 'e', castmethod => 'f' },
 
+# Allow explicit coercions between xid8 and xid
+{ castsource => 'xid8', casttarget => 'xid', castfunc => 'xid(xid8)',
+  castcontext => 'e', castmethod => 'f' },
+
 # OID category: allow implicit conversion from any integral type (including
 # int8, to support OID literals > 2G) to OID, as well as assignment coercion
 # from OID to int4 or int8.  Similarly for each OID-alias type.  Also allow
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index ab2f50c9eb..f2342bb328 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -168,6 +168,10 @@
   opcintype => 'tid' },
 { opcmethod => 'hash', opcname => 'xid_ops', opcfamily => 'hash/xid_ops',
   opcintype => 'xid' },
+{ opcmethod => 'hash', opcname => 'xid8_ops', opcfamily => 'hash/xid8_ops',
+  opcintype => 'xid8' },
+{ opcmethod => 'btree', opcname => 'xid8_ops', opcfamily => 'btree/xid8_ops',
+  opcintype => 'xid8' },
 { opcmethod => 'hash', opcname => 'cid_ops', opcfamily => 'hash/cid_ops',
   opcintype => 'cid' },
 { opcmethod => 'hash', opcname => 'tid_ops', opcfamily => 'hash/tid_ops',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 65c7fedf23..00ada7e48f 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -193,6 +193,31 @@
   oprname => '<>', oprleft => 'xid', oprright => 'int4', oprresult => 'bool',
   oprnegate => '=(xid,int4)', oprcode => 'xidneqint4', oprrest => 'neqsel',
   oprjoin => 'neqjoinsel' },
+{ oid => '9418', descr => 'equal',
+  oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'xid8',
+  oprright => 'xid8', oprresult => 'bool', oprcom => '=(xid8,xid8)',
+  oprnegate => '<>(xid8,xid8)', oprcode => 'xid8eq', oprrest => 'eqsel',
+  oprjoin => 'eqjoinsel' },
+{ oid => '9422', descr => 'not equal',
+  oprname => '<>', oprleft => 'xid8', oprright => 'xid8',
+  oprresult => 'bool', oprcom => '<>(xid8,xid8)', oprnegate => '=(xid8,xid8)',
+  oprcode => 'xid8ne', oprrest => 'neqsel', oprjoin => 'neqjoinsel' },
+{ oid => '9432', descr => 'less than',
+  oprname => '<', oprleft => 'xid8', oprright => 'xid8', oprresult => 'bool',
+  oprcom => '>(xid8,xid8)', oprnegate => '>=(xid8,xid8)', oprcode => 'xid8lt',
+  oprrest => 'scalarltsel', oprjoin => 'scalarltjoinsel' },
+{ oid => '9433', descr => 'greater than',
+  oprname => '>', oprleft => 'xid8', oprright => 'xid8', oprresult => 'bool',
+  oprcom => '<(xid8,xid8)', oprnegate => '<=(xid8,xid8)', oprcode => 'xid8gt',
+  oprrest => 'scalargtsel', oprjoin => 'scalargtjoinsel' },
+{ oid => '9434', descr => 'less than or equal',
+  oprname => '<=', oprleft => 'xid8', oprright => 'xid8', oprresult => 'bool',
+  oprcom => '>=(xid8,xid8)', oprnegate => '>(xid8,xid8)', oprcode => 'xid8le',
+  oprrest => 'scalarlesel', oprjoin => 'scalarlejoinsel' },
+{ oid => '9435', descr => 'greater than or equal',
+  oprname => '>=', oprleft => 'xid8', oprright => 'xid8', oprresult => 'bool',
+  oprcom => '<=(xid8,xid8)', oprnegate => '<(xid8,xid8)', oprcode => 'xid8ge',
+  oprrest => 'scalargesel', oprjoin => 'scalargejoinsel' },
 { oid => '388', descr => 'factorial',
   oprname => '!', oprkind => 'r', oprleft => 'int8', oprright => '0',
   oprresult => 'numeric', oprcode => 'numeric_fac' },
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 26227df216..4004138d77 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -110,6 +110,10 @@
   opfmethod => 'btree', opfname => 'tid_ops' },
 { oid => '2225',
   opfmethod => 'hash', opfname => 'xid_ops' },
+{ oid => '8164',
+  opfmethod => 'hash', opfname => 'xid8_ops' },
+{ oid => '9322',
+  opfmethod => 'btree', opfname => 'xid8_ops' },
 { oid => '2226',
   opfmethod => 'hash', opfname => 'cid_ops' },
 { oid => '2227',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 55ef716858..3474c04bdf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -112,6 +112,18 @@
 { oid => '51', descr => 'I/O',
   proname => 'xidout', prorettype => 'cstring', proargtypes => 'xid',
   prosrc => 'xidout' },
+{ oid => '9420', descr => 'I/O',
+  proname => 'xid8in', prorettype => 'xid8', proargtypes => 'cstring',
+  prosrc => 'xid8in' },
+{ oid => '9554', descr => 'I/O',
+  proname => 'xid8out', prorettype => 'cstring', proargtypes => 'xid8',
+  prosrc => 'xid8out' },
+{ oid => '9555', descr => 'I/O',
+  proname => 'xid8recv', prorettype => 'xid8', proargtypes => 'internal',
+  prosrc => 'xid8recv' },
+{ oid => '9556', descr => 'I/O',
+  proname => 'xid8send', prorettype => 'bytea', proargtypes => 'xid8',
+  prosrc => 'xid8send' },
 { oid => '52', descr => 'I/O',
   proname => 'cidin', prorettype => 'cid', proargtypes => 'cstring',
   prosrc => 'cidin' },
@@ -163,6 +175,30 @@
 { oid => '3308',
   proname => 'xidneq', proleakproof => 't', prorettype => 'bool',
   proargtypes => 'xid xid', prosrc => 'xidneq' },
+{ oid => '9557',
+  proname => 'xid8eq', proleakproof => 't', prorettype => 'bool',
+  proargtypes => 'xid8 xid8', prosrc => 'xid8eq' },
+{ oid => '9558',
+  proname => 'xid8ne', proleakproof => 't', prorettype => 'bool',
+  proargtypes => 'xid8 xid8', prosrc => 'xid8ne' },
+{ oid => '8295',
+  proname => 'xid8lt', proleakproof => 't', prorettype => 'bool',
+  proargtypes => 'xid8 xid8', prosrc => 'xid8lt' },
+{ oid => '8296',
+  proname => 'xid8gt', proleakproof => 't', prorettype => 'bool',
+  proargtypes => 'xid8 xid8', prosrc => 'xid8gt' },
+{ oid => '8297',
+  proname => 'xid8le', proleakproof => 't', prorettype => 'bool',
+  proargtypes => 'xid8 xid8', prosrc => 'xid8le' },
+{ oid => '8298',
+  proname => 'xid8ge', proleakproof => 't', prorettype => 'bool',
+  proargtypes => 'xid8 xid8', prosrc => 'xid8ge' },
+{ oid => '9912', descr => 'less-equal-greater',
+  proname => 'xid8cmp', proleakproof => 't', prorettype => 'int4',
+  proargtypes => 'xid8 xid8', prosrc => 'xid8cmp' },
+{ oid => '9421', descr => 'convert xid8 to xid',
+  proname => 'xid', prorettype => 'xid', proargtypes => 'xid8',
+  prosrc => 'xid8toxid' },
 { oid => '69',
   proname => 'cideq', proleakproof => 't', prorettype => 'bool',
   proargtypes => 'cid cid', prosrc => 'cideq' },
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index 2e6110e3f2..a1f441b8da 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -177,6 +177,10 @@
   typtype => 'p', typcategory => 'P', typinput => 'pg_ddl_command_in',
   typoutput => 'pg_ddl_command_out', typreceive => 'pg_ddl_command_recv',
   typsend => 'pg_ddl_command_send', typalign => 'ALIGNOF_POINTER' },
+{ oid => '9419', array_type_oid => '271', descr => 'full transaction id',
+  typname => 'xid8', typlen => '8', typbyval => 'FLOAT8PASSBYVAL',
+  typcategory => 'U', typinput => 'xid8in', typoutput => 'xid8out',
+  typreceive => 'xid8recv', typsend => 'xid8send', typalign => 'd' },
 
 # OIDS 600 - 699
 
diff --git a/src/include/utils/xid8.h b/src/include/utils/xid8.h
new file mode 100644
index 0000000000..288e62de9c
--- /dev/null
+++ b/src/include/utils/xid8.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * xid8.h
+ *	  Header file for the "xid8" ADT.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/utils/xid8.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef XID8_H
+#define XID8_H
+
+#include "access/transam.h"
+
+#define DatumGetFullTransactionId(X) (FullTransactionIdFromU64(DatumGetUInt64(X)))
+#define FullTransactionIdGetDatum(X) (UInt64GetDatum(U64FromFullTransactionId(X)))
+#define PG_GETARG_FULLTRANSACTIONID(X) DatumGetFullTransactionId(PG_GETARG_DATUM(X))
+#define PG_RETURN_FULLTRANSACTIONID(X) return FullTransactionIdGetDatum(X)
+
+#endif							/* XID8_H */
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 2efd7d7ec7..0c03afe53d 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -832,6 +832,13 @@ macaddr8_gt(macaddr8,macaddr8)
 macaddr8_ge(macaddr8,macaddr8)
 macaddr8_ne(macaddr8,macaddr8)
 macaddr8_cmp(macaddr8,macaddr8)
+xid8lt(xid8,xid8)
+xid8gt(xid8,xid8)
+xid8le(xid8,xid8)
+xid8ge(xid8,xid8)
+xid8eq(xid8,xid8)
+xid8ne(xid8,xid8)
+xid8cmp(xid8,xid8)
 -- restore normal output mode
 \a\t
 -- List of functions used by libpq's fe-lobj.c
diff --git a/src/test/regress/expected/xid.out b/src/test/regress/expected/xid.out
new file mode 100644
index 0000000000..e9e1fa8259
--- /dev/null
+++ b/src/test/regress/expected/xid.out
@@ -0,0 +1,136 @@
+-- xid and xid8
+-- values in range, in octal, decimal, hex
+select '010'::xid,
+       '42'::xid,
+       '0xffffffff'::xid,
+       '-1'::xid,
+	   '010'::xid8,
+	   '42'::xid8,
+	   '0xffffffffffffffff'::xid8,
+	   '-1'::xid8;
+ xid | xid |    xid     |    xid     | xid8 | xid8 |         xid8         |         xid8         
+-----+-----+------------+------------+------+------+----------------------+----------------------
+   8 |  42 | 4294967295 | 4294967295 |    8 |   42 | 18446744073709551615 | 18446744073709551615
+(1 row)
+
+-- garbage values are not yet rejected (perhaps they should be)
+select ''::xid;
+ xid 
+-----
+   0
+(1 row)
+
+select 'asdf'::xid;
+ xid 
+-----
+   0
+(1 row)
+
+select ''::xid8;
+ xid8 
+------
+    0
+(1 row)
+
+select 'asdf'::xid8;
+ xid8 
+------
+    0
+(1 row)
+
+-- equality
+select '1'::xid = '1'::xid;
+ ?column? 
+----------
+ t
+(1 row)
+
+select '1'::xid != '1'::xid;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '1'::xid8 = '1'::xid8;
+ ?column? 
+----------
+ t
+(1 row)
+
+select '1'::xid8 != '1'::xid8;
+ ?column? 
+----------
+ f
+(1 row)
+
+-- conversion
+select '1'::xid = '1'::xid8::xid;
+ ?column? 
+----------
+ t
+(1 row)
+
+select '1'::xid != '1'::xid8::xid;
+ ?column? 
+----------
+ f
+(1 row)
+
+-- we don't want relational operators for xid, due to use of modular arithmetic
+select '1'::xid < '2'::xid;
+ERROR:  operator does not exist: xid < xid
+LINE 1: select '1'::xid < '2'::xid;
+                        ^
+HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+select '1'::xid <= '2'::xid;
+ERROR:  operator does not exist: xid <= xid
+LINE 1: select '1'::xid <= '2'::xid;
+                        ^
+HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+select '1'::xid > '2'::xid;
+ERROR:  operator does not exist: xid > xid
+LINE 1: select '1'::xid > '2'::xid;
+                        ^
+HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+select '1'::xid >= '2'::xid;
+ERROR:  operator does not exist: xid >= xid
+LINE 1: select '1'::xid >= '2'::xid;
+                        ^
+HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+-- we want them for xid8 though
+select '1'::xid8 < '2'::xid8, '2'::xid8 < '2'::xid8, '2'::xid8 < '1'::xid8;
+ ?column? | ?column? | ?column? 
+----------+----------+----------
+ t        | f        | f
+(1 row)
+
+select '1'::xid8 <= '2'::xid8, '2'::xid8 <= '2'::xid8, '2'::xid8 <= '1'::xid8;
+ ?column? | ?column? | ?column? 
+----------+----------+----------
+ t        | t        | f
+(1 row)
+
+select '1'::xid8 > '2'::xid8, '2'::xid8 > '2'::xid8, '2'::xid8 > '1'::xid8;
+ ?column? | ?column? | ?column? 
+----------+----------+----------
+ f        | f        | t
+(1 row)
+
+select '1'::xid8 >= '2'::xid8, '2'::xid8 >= '2'::xid8, '2'::xid8 >= '1'::xid8;
+ ?column? | ?column? | ?column? 
+----------+----------+----------
+ f        | t        | t
+(1 row)
+
+-- we also have a 3way compare for btrees
+select xid8cmp('1', '2'), xid8cmp('2', '2'), xid8cmp('2', '1');
+ xid8cmp | xid8cmp | xid8cmp 
+---------+---------+---------
+      -1 |       0 |       1
+(1 row)
+
+-- xid8 has btree and hash opclasses
+create table xid8_t1 (x xid8);
+create index on xid8_t1 using btree(x);
+create index on xid8_t1 using hash(x);
+drop table xid8_t1;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd3ea..b4c100b1c0 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -20,7 +20,7 @@ test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeri
 # strings depends on char, varchar and text
 # numerology depends on int2, int4, int8, float4, float8
 # ----------
-test: strings numerology point lseg line box path polygon circle date time timetz timestamp timestamptz interval inet macaddr macaddr8 tstypes
+test: strings numerology point lseg line box path polygon circle date time timetz timestamp timestamptz interval inet macaddr macaddr8 tstypes xid
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index acba391332..017aa3888f 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -10,6 +10,7 @@ test: int2
 test: int4
 test: int8
 test: oid
+test: xid
 test: float4
 test: float8
 test: bit
diff --git a/src/test/regress/sql/xid.sql b/src/test/regress/sql/xid.sql
new file mode 100644
index 0000000000..a4fbca5176
--- /dev/null
+++ b/src/test/regress/sql/xid.sql
@@ -0,0 +1,48 @@
+-- xid and xid8
+
+-- values in range, in octal, decimal, hex
+select '010'::xid,
+       '42'::xid,
+       '0xffffffff'::xid,
+       '-1'::xid,
+	   '010'::xid8,
+	   '42'::xid8,
+	   '0xffffffffffffffff'::xid8,
+	   '-1'::xid8;
+
+-- garbage values are not yet rejected (perhaps they should be)
+select ''::xid;
+select 'asdf'::xid;
+select ''::xid8;
+select 'asdf'::xid8;
+
+-- equality
+select '1'::xid = '1'::xid;
+select '1'::xid != '1'::xid;
+select '1'::xid8 = '1'::xid8;
+select '1'::xid8 != '1'::xid8;
+
+-- conversion
+select '1'::xid = '1'::xid8::xid;
+select '1'::xid != '1'::xid8::xid;
+
+-- we don't want relational operators for xid, due to use of modular arithmetic
+select '1'::xid < '2'::xid;
+select '1'::xid <= '2'::xid;
+select '1'::xid > '2'::xid;
+select '1'::xid >= '2'::xid;
+
+-- we want them for xid8 though
+select '1'::xid8 < '2'::xid8, '2'::xid8 < '2'::xid8, '2'::xid8 < '1'::xid8;
+select '1'::xid8 <= '2'::xid8, '2'::xid8 <= '2'::xid8, '2'::xid8 <= '1'::xid8;
+select '1'::xid8 > '2'::xid8, '2'::xid8 > '2'::xid8, '2'::xid8 > '1'::xid8;
+select '1'::xid8 >= '2'::xid8, '2'::xid8 >= '2'::xid8, '2'::xid8 >= '1'::xid8;
+
+-- we also have a 3way compare for btrees
+select xid8cmp('1', '2'), xid8cmp('2', '2'), xid8cmp('2', '1');
+
+-- xid8 has btree and hash opclasses
+create table xid8_t1 (x xid8);
+create index on xid8_t1 using btree(x);
+create index on xid8_t1 using hash(x);
+drop table xid8_t1;
-- 
2.20.1

From 05210fce0563a2424b1b70f2ed94107eb60406f1 Mon Sep 17 00:00:00 2001
From: Thomas Munro <tmu...@postgresql.org>
Date: Thu, 2 Apr 2020 15:18:18 +1300
Subject: [PATCH v8 2/2] Introduce xid8_XXX functions to replace txid_XXX.

The txid_XXX family of fmgr functions exposes 64 bit transaction IDs to
users as int8.  Now that we have an SQL type for FullTransactionId,
define a new set of functions xid8_XXX.  Keep the old functions around
too, for now.

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 are the ones using the wrong signedness,
and since we'll presumably drop the older ones after a reasonable period
of time, it seems reasonable to switch to FullTransactionId internally
and share the code for both.

Reviewed-by: Fujii Masao <masao.fu...@oss.nttdata.com>
Reviewed-by: Takao Fujii <btfujii...@oss.nttdata.com>
Reviewed-by: Yoshikazu Imai <imai.yoshik...@fujitsu.com>
Reviewed-by: Mark Dilger <hornschnor...@gmail.com>
Discussion: https://postgr.es/m/20190725000636.666m5mad25wfbrri%40alap3.anarazel.de
---
 doc/src/sgml/datatype.sgml                    |   8 +-
 doc/src/sgml/func.sgml                        | 140 +++++-
 doc/src/sgml/logicaldecoding.sgml             |   2 +-
 doc/src/sgml/monitoring.sgml                  |   2 +-
 src/backend/utils/adt/Makefile                |   2 +-
 src/backend/utils/adt/{txid.c => xid8funcs.c} | 414 ++++++++----------
 src/include/catalog/pg_proc.dat               |  67 ++-
 src/include/catalog/pg_type.dat               |   5 +
 src/test/regress/expected/opr_sanity.out      |  11 +-
 src/test/regress/expected/txid.out            |  13 +-
 src/test/regress/expected/xid.out             | 326 ++++++++++++++
 src/test/regress/sql/txid.sql                 |   3 +
 src/test/regress/sql/xid.sql                  | 104 +++++
 13 files changed, 814 insertions(+), 283 deletions(-)
 rename src/backend/utils/adt/{txid.c => xid8funcs.c} (55%)

diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 89f3a7c119..91c9202458 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -279,7 +279,7 @@
       <row>
        <entry><type>txid_snapshot</type></entry>
        <entry></entry>
-       <entry>user-level transaction ID snapshot</entry>
+       <entry>user-level transaction ID snapshot (see also <type>xid8_snapshot</type>)</entry>
       </row>
 
       <row>
@@ -288,6 +288,12 @@
        <entry>universally unique identifier</entry>
       </row>
 
+      <row>
+       <entry><type>xid8_snapshot</type></entry>
+       <entry></entry>
+       <entry>user-level transaction ID snapshot</entry>
+      </row>
+
       <row>
        <entry><type>xml</type></entry>
        <entry></entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index cbfd2a762e..a16aba20eb 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18950,6 +18950,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>
@@ -18983,12 +19015,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 xid8</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 backward 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">
@@ -19000,42 +19094,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>
@@ -19046,13 +19140,15 @@ SELECT collation for ('foo' COLLATE "de_DE");
     wraps around every 4 billion transactions.  However, these functions
     export a 64-bit format that is extended with an <quote>epoch</quote> counter
     so it will not wrap around during the life of an installation.
-    The data type used by these functions, <type>txid_snapshot</type>,
+    The data type used by these functions, <type>xid8_snapshot</type>,
     stores information about transaction ID
     visibility at a particular moment in time.  Its components are
-    described in <xref linkend="functions-txid-snapshot-parts"/>.
+    described in <xref linkend="functions-xid8-snapshot-parts"/>.  The
+    <type>xid8</type> values it contains can be converted to <type>xid</type>
+    by casting, if required.
    </para>
 
-   <table id="functions-txid-snapshot-parts">
+   <table id="functions-xid8-snapshot-parts">
     <title>Snapshot Components</title>
     <tgroup cols="2">
      <thead>
@@ -19067,7 +19163,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
       <row>
        <entry><type>xmin</type></entry>
        <entry>
-         Earliest transaction ID (txid) that is still active.  All earlier
+         Earliest transaction ID (xid8) that is still active.  All earlier
          transactions will either be committed and visible, or rolled
          back and dead.
        </entry>
@@ -19076,7 +19172,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
       <row>
        <entry><type>xmax</type></entry>
        <entry>
-        First as-yet-unassigned txid.  All txids greater than or equal to this
+        First as-yet-unassigned xid8.  All xid8s greater than or equal to this
         are not yet started as of the time of the snapshot, and thus invisible.
        </entry>
       </row>
@@ -19084,14 +19180,14 @@ SELECT collation for ('foo' COLLATE "de_DE");
       <row>
        <entry><type>xip_list</type></entry>
        <entry>
-        Active txids at the time of the snapshot.  The list
-        includes only those active txids between <literal>xmin</literal>
-        and <literal>xmax</literal>; there might be active txids higher
-        than <literal>xmax</literal>.  A txid that is <literal>xmin &lt;= txid &lt;
+        Active xid8s at the time of the snapshot.  The list
+        includes only those active xid8s between <literal>xmin</literal>
+        and <literal>xmax</literal>; there might be active xid8s higher
+        than <literal>xmax</literal>.  An xid8 that is <literal>xmin &lt;= xid8 &lt;
         xmax</literal> and not in this list was already completed
         at the time of the snapshot, and thus either visible or
         dead according to its commit status.  The list does not
-        include txids of subtransactions.
+        include xid8s of subtransactions.
        </entry>
       </row>
 
@@ -19100,14 +19196,14 @@ SELECT collation for ('foo' COLLATE "de_DE");
    </table>
 
    <para>
-    <type>txid_snapshot</type>'s textual representation is
+    <type>xid8_snapshot</type>'s textual representation is
     <literal><replaceable>xmin</replaceable>:<replaceable>xmax</replaceable>:<replaceable>xip_list</replaceable></literal>.
     For example <literal>10:20:10,14,15</literal> means
     <literal>xmin=10, xmax=20, xip_list=10, 14, 15</literal>.
    </para>
 
    <para>
-    <function>txid_status(bigint)</function> reports the commit status of a recent
+    <function>xid8_status(xid8)</function> reports the commit status of a recent
     transaction.  Applications may use it to determine whether a transaction
     committed or aborted when the application and database server become
     disconnected while a <literal>COMMIT</literal> is in progress.
@@ -19121,7 +19217,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
     transactions are reported as <literal>in progress</literal>; applications must
     check <link
     linkend="view-pg-prepared-xacts"><literal>pg_prepared_xacts</literal></link> if they
-    need to determine whether the txid is a prepared transaction.
+    need to determine whether the xid8 is a prepared transaction.
    </para>
 
    <para>
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index bce6d379bf..e8e6e36a27 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -418,7 +418,7 @@ CREATE TABLE another_catalog_table(data text) WITH (user_catalog_table = true);
 </programlisting>
      Any actions leading to transaction ID assignment are prohibited. That, among others,
      includes writing to tables, performing DDL changes, and
-     calling <literal>txid_current()</literal>.
+     calling <literal>xid8_current()</literal>.
     </para>
    </sect2>
 
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 28ceb04d33..25d905d111 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -1112,7 +1112,7 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
         </row>
         <row>
          <entry><literal>CLogTruncationLock</literal></entry>
-         <entry>Waiting to execute <function>txid_status</function> or update
+         <entry>Waiting to execute <function>xid8_status</function> or update
          the oldest transaction id available to it.</entry>
         </row>
         <row>
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 13efa9338c..5d2aca8cfe 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -101,7 +101,6 @@ OBJS = \
 	tsvector.o \
 	tsvector_op.o \
 	tsvector_parser.o \
-	txid.o \
 	uuid.o \
 	varbit.o \
 	varchar.o \
@@ -109,6 +108,7 @@ OBJS = \
 	version.o \
 	windowfuncs.o \
 	xid.o \
+	xid8funcs.o \
 	xml.o
 
 jsonpath_scan.c: FLEXFLAGS = -CF -p -p
diff --git a/src/backend/utils/adt/txid.c b/src/backend/utils/adt/xid8funcs.c
similarity index 55%
rename from src/backend/utils/adt/txid.c
rename to src/backend/utils/adt/xid8funcs.c
index 33272f8030..873604eeb8 100644
--- a/src/backend/utils/adt/txid.c
+++ b/src/backend/utils/adt/xid8funcs.c
@@ -1,14 +1,19 @@
 /*-------------------------------------------------------------------------
- * txid.c
+ * xid8funcs.c
  *
  *	Export internal transaction IDs to user level.
  *
- * Note that only top-level transaction IDs are ever converted to TXID.
- * This is important because TXIDs frequently persist beyond the global
+ * Note that only top-level transaction IDs are exposed to user sessions.
+ * This is important because xid8s frequently persist beyond the global
  * xmin horizon, or may even be shipped to other machines, so we cannot
  * 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.  The txid_XXX variants should
+ * eventually be dropped.
+ *
  *
  *	Copyright (c) 2003-2020, PostgreSQL Global Development Group
  *	Author: Jan Wieck, Afilias USA INC.
@@ -34,25 +39,18 @@
 #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
+ * If defined, use bsearch() function for searching for xid8s in snapshots
  * that have more than the specified number of values.
  */
 #define USE_BSEARCH_IF_NXIP_GREATER 30
 
 
 /*
- * Snapshot containing 8byte txids.
+ * Snapshot containing FullTransactionIds.
  */
 typedef struct
 {
@@ -63,39 +61,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];
-} TxidSnapshot;
-
-#define TXID_SNAPSHOT_SIZE(nxip) \
-	(offsetof(TxidSnapshot, xip) + sizeof(txid) * (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;
-
+	uint32		nxip;			/* number of fxids in xip array */
+	FullTransactionId xmin;
+	FullTransactionId xmax;
+	/* in-progress fxids, xmin <= xip[i] < xmax: */
+	FullTransactionId xip[FLEXIBLE_ARRAY_MEMBER];
+} Xid8Snapshot;
 
-/*
- * 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);
-}
+#define XID8_SNAPSHOT_SIZE(nxip) \
+	(offsetof(Xid8Snapshot, xip) + sizeof(FullTransactionId) * (nxip))
+#define XID8_SNAPSHOT_MAX_NXIP \
+	((MaxAllocSize - offsetof(Xid8Snapshot, xip)) / sizeof(FullTransactionId))
 
 /*
  * Helper to get a TransactionId from a 64-bit xid with wraparound detection.
@@ -111,10 +87,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 +110,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 +141,46 @@ TransactionIdInRecentPast(uint64 xid_with_epoch, TransactionId *extracted_xid)
 }
 
 /*
- * do a TransactionId -> txid conversion for an XID near the given epoch
+ * Convert a TransactionId obtained from a snapshot held by the caller to a
+ * FullTransactionId.  Use next_fxid as a reference FullTransactionId, so that
+ * we can compute the high order bits.  It must have been obtained by the
+ * caller with ReadNextFullTransactionId() after the snapshot was created.
  */
-static txid
-convert_xid(TransactionId xid, const TxidEpoch *state)
+static FullTransactionId
+widen_snapshot_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 */
+	/* Special transaction ID. */
 	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))
+	/*
+	 * The 64 bit result must be <= next_fxid, since next_fxid hadn't been
+	 * issued yet when the snapshot was created.  Every TransactionId in the
+	 * snapshot must therefore be from the same epoch as next_fxid, or the
+	 * epoch before.  We know this because next_fxid is never allow to get more
+	 * than one epoch ahead of the TransactionIds in any snapshot.
+	 */
+	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;
 }
@@ -211,31 +193,33 @@ cmp_txid(const void *aa, const void *bb)
  * will not be used.
  */
 static void
-sort_snapshot(TxidSnapshot *snap)
+sort_snapshot(Xid8Snapshot *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 Xid8Snapshot *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 +230,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;
@@ -254,13 +238,13 @@ is_visible_txid(txid value, const TxidSnapshot *snap)
 }
 
 /*
- * helper functions to use StringInfo for TxidSnapshot creation.
+ * helper functions to use StringInfo for Xid8Snapshot creation.
  */
 
 static StringInfo
-buf_init(txid xmin, txid xmax)
+buf_init(FullTransactionId xmin, FullTransactionId xmax)
 {
-	TxidSnapshot snap;
+	Xid8Snapshot snap;
 	StringInfo	buf;
 
 	snap.xmin = xmin;
@@ -268,25 +252,25 @@ buf_init(txid xmin, txid xmax)
 	snap.nxip = 0;
 
 	buf = makeStringInfo();
-	appendBinaryStringInfo(buf, (char *) &snap, TXID_SNAPSHOT_SIZE(0));
+	appendBinaryStringInfo(buf, (char *) &snap, XID8_SNAPSHOT_SIZE(0));
 	return buf;
 }
 
 static void
-buf_add_txid(StringInfo buf, txid xid)
+buf_add_txid(StringInfo buf, FullTransactionId fxid)
 {
-	TxidSnapshot *snap = (TxidSnapshot *) buf->data;
+	Xid8Snapshot *snap = (Xid8Snapshot *) buf->data;
 
 	/* do this before possible realloc */
 	snap->nxip++;
 
-	appendBinaryStringInfo(buf, (char *) &xid, sizeof(xid));
+	appendBinaryStringInfo(buf, (char *) &fxid, sizeof(fxid));
 }
 
-static TxidSnapshot *
+static Xid8Snapshot *
 buf_finalize(StringInfo buf)
 {
-	TxidSnapshot *snap = (TxidSnapshot *) buf->data;
+	Xid8Snapshot *snap = (Xid8Snapshot *) buf->data;
 
 	SET_VARSIZE(snap, buf->len);
 
@@ -297,68 +281,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 *
+static Xid8Snapshot *
 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 +318,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,108 +344,90 @@ 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 */
 }
 
 /*
  * Public functions.
  *
- * txid_current() and txid_current_snapshot() are the only ones that
+ * xid8_current() and xid8_current_snapshot() are the only ones that
  * communicate with core xid machinery.  All the others work on data
  * returned by them.
  */
 
 /*
- * txid_current() returns int8
+ * xid8_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)
+xid8_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);
+	PreventCommandDuringRecovery("xid8_current()");
 
-	val = convert_xid(GetTopTransactionId(), &state);
-
-	PG_RETURN_INT64(val);
+	PG_RETURN_FULLTRANSACTIONID(GetTopFullTransactionId());
 }
 
 /*
- * Same as txid_current() but doesn't assign a new xid if there isn't one
+ * Same as xid8_current() but doesn't assign a new xid if there isn't one
  * yet.
  */
 Datum
-txid_current_if_assigned(PG_FUNCTION_ARGS)
+xid8_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);
 }
 
 /*
- * txid_current_snapshot() returns txid_snapshot
+ * xid8_current_snapshot() returns xid8_snapshot
  *
- *		Return current snapshot in TXID format
+ *		Return current snapshot
  *
  * Note that only top-transaction XIDs are included in the snapshot.
  */
 Datum
-txid_current_snapshot(PG_FUNCTION_ARGS)
+xid8_current_snapshot(PG_FUNCTION_ARGS)
 {
-	TxidSnapshot *snap;
+	Xid8Snapshot *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.
 	 */
-	StaticAssertStmt(MAX_BACKENDS * 2 <= TXID_SNAPSHOT_MAX_NXIP,
-					 "possible overflow in txid_current_snapshot()");
+	StaticAssertStmt(MAX_BACKENDS * 2 <= XID8_SNAPSHOT_MAX_NXIP,
+					 "possible overflow in xid8_current_snapshot()");
 
 	/* allocate */
 	nxip = cur->xcnt;
-	snap = palloc(TXID_SNAPSHOT_SIZE(nxip));
+	snap = palloc(XID8_SNAPSHOT_SIZE(nxip));
 
 	/* fill */
-	snap->xmin = convert_xid(cur->xmin, &state);
-	snap->xmax = convert_xid(cur->xmax, &state);
+	snap->xmin = widen_snapshot_xid(cur->xmin, next_fxid);
+	snap->xmax = widen_snapshot_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] = widen_snapshot_xid(cur->xip[i], next_fxid);
 
 	/*
 	 * We want them guaranteed to be in ascending order.  This also removes
@@ -505,21 +439,21 @@ txid_current_snapshot(PG_FUNCTION_ARGS)
 	sort_snapshot(snap);
 
 	/* set size after sorting, because it may have removed duplicate xips */
-	SET_VARSIZE(snap, TXID_SNAPSHOT_SIZE(snap->nxip));
+	SET_VARSIZE(snap, XID8_SNAPSHOT_SIZE(snap->nxip));
 
 	PG_RETURN_POINTER(snap);
 }
 
 /*
- * txid_snapshot_in(cstring) returns txid_snapshot
+ * xid8_snapshot_in(cstring) returns xid8_snapshot
  *
- *		input function for type txid_snapshot
+ *		input function for type xid8_snapshot
  */
 Datum
-txid_snapshot_in(PG_FUNCTION_ARGS)
+xid8_snapshot_in(PG_FUNCTION_ARGS)
 {
 	char	   *str = PG_GETARG_CSTRING(0);
-	TxidSnapshot *snap;
+	Xid8Snapshot *snap;
 
 	snap = parse_snapshot(str);
 
@@ -527,73 +461,81 @@ txid_snapshot_in(PG_FUNCTION_ARGS)
 }
 
 /*
- * txid_snapshot_out(txid_snapshot) returns cstring
+ * xid8_snapshot_out(xid8_snapshot) returns cstring
  *
- *		output function for type txid_snapshot
+ *		output function for type xid8_snapshot
  */
 Datum
-txid_snapshot_out(PG_FUNCTION_ARGS)
+xid8_snapshot_out(PG_FUNCTION_ARGS)
 {
-	TxidSnapshot *snap = (TxidSnapshot *) PG_GETARG_VARLENA_P(0);
+	Xid8Snapshot *snap = (Xid8Snapshot *) PG_GETARG_VARLENA_P(0);
 	StringInfoData str;
 	uint32		i;
 
 	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);
 }
 
 /*
- * txid_snapshot_recv(internal) returns txid_snapshot
+ * xid8_snapshot_recv(internal) returns xid8_snapshot
  *
- *		binary input function for type txid_snapshot
+ *		binary input function for type xid8_snapshot
  *
  *		format: int4 nxip, int8 xmin, int8 xmax, int8 xip
  */
 Datum
-txid_snapshot_recv(PG_FUNCTION_ARGS)
+xid8_snapshot_recv(PG_FUNCTION_ARGS)
 {
 	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
-	TxidSnapshot *snap;
-	txid		last = 0;
+	Xid8Snapshot *snap;
+	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)
+	if (nxip < 0 || nxip > XID8_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));
+	snap = palloc(XID8_SNAPSHOT_SIZE(nxip));
 	snap->xmin = xmin;
 	snap->xmax = xmax;
 
 	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--;
@@ -604,95 +546,95 @@ txid_snapshot_recv(PG_FUNCTION_ARGS)
 		last = cur;
 	}
 	snap->nxip = nxip;
-	SET_VARSIZE(snap, TXID_SNAPSHOT_SIZE(nxip));
+	SET_VARSIZE(snap, XID8_SNAPSHOT_SIZE(nxip));
 	PG_RETURN_POINTER(snap);
 
 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 */
 }
 
 /*
- * txid_snapshot_send(txid_snapshot) returns bytea
+ * xid8_snapshot_send(xid8_snapshot) returns bytea
  *
- *		binary output function for type txid_snapshot
+ *		binary output function for type xid8_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)
+xid8_snapshot_send(PG_FUNCTION_ARGS)
 {
-	TxidSnapshot *snap = (TxidSnapshot *) PG_GETARG_VARLENA_P(0);
+	Xid8Snapshot *snap = (Xid8Snapshot *) PG_GETARG_VARLENA_P(0);
 	StringInfoData buf;
 	uint32		i;
 
 	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
+ * xid8_visible_in_snapshot(xid8, xid8_snapshot) returns bool
  *
  *		is txid visible in snapshot ?
  */
 Datum
-txid_visible_in_snapshot(PG_FUNCTION_ARGS)
+xid8_visible_in_snapshot(PG_FUNCTION_ARGS)
 {
-	txid		value = PG_GETARG_INT64(0);
-	TxidSnapshot *snap = (TxidSnapshot *) PG_GETARG_VARLENA_P(1);
+	FullTransactionId value = PG_GETARG_FULLTRANSACTIONID(0);
+	Xid8Snapshot *snap = (Xid8Snapshot *) 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
+ * xid8_snapshot_xmin(xid8_snapshot) returns xid8
  *
  *		return snapshot's xmin
  */
 Datum
-txid_snapshot_xmin(PG_FUNCTION_ARGS)
+xid8_snapshot_xmin(PG_FUNCTION_ARGS)
 {
-	TxidSnapshot *snap = (TxidSnapshot *) PG_GETARG_VARLENA_P(0);
+	Xid8Snapshot *snap = (Xid8Snapshot *) PG_GETARG_VARLENA_P(0);
 
-	PG_RETURN_INT64(snap->xmin);
+	PG_RETURN_FULLTRANSACTIONID(snap->xmin);
 }
 
 /*
- * txid_snapshot_xmax(txid_snapshot) returns int8
+ * xid8_snapshot_xmax(xid8_snapshot) returns xid8
  *
  *		return snapshot's xmax
  */
 Datum
-txid_snapshot_xmax(PG_FUNCTION_ARGS)
+xid8_snapshot_xmax(PG_FUNCTION_ARGS)
 {
-	TxidSnapshot *snap = (TxidSnapshot *) PG_GETARG_VARLENA_P(0);
+	Xid8Snapshot *snap = (Xid8Snapshot *) PG_GETARG_VARLENA_P(0);
 
-	PG_RETURN_INT64(snap->xmax);
+	PG_RETURN_FULLTRANSACTIONID(snap->xmax);
 }
 
 /*
- * txid_snapshot_xip(txid_snapshot) returns setof int8
+ * xid8_snapshot_xip(xid8_snapshot) returns setof xid8
  *
- *		return in-progress TXIDs in snapshot.
+ *		return in-progress xid8s in snapshot.
  */
 Datum
-txid_snapshot_xip(PG_FUNCTION_ARGS)
+xid8_snapshot_xip(PG_FUNCTION_ARGS)
 {
 	FuncCallContext *fctx;
-	TxidSnapshot *snap;
-	txid		value;
+	Xid8Snapshot *snap;
+	FullTransactionId value;
 
 	/* on first call initialize fctx and get copy of snapshot */
 	if (SRF_IS_FIRSTCALL())
 	{
-		TxidSnapshot *arg = (TxidSnapshot *) PG_GETARG_VARLENA_P(0);
+		Xid8Snapshot *arg = (Xid8Snapshot *) PG_GETARG_VARLENA_P(0);
 
 		fctx = SRF_FIRSTCALL_INIT();
 
@@ -709,7 +651,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
 	{
@@ -728,10 +670,10 @@ txid_snapshot_xip(PG_FUNCTION_ARGS)
  * though the parent xact may still be in progress or may have aborted.
  */
 Datum
-txid_status(PG_FUNCTION_ARGS)
+xid8_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 +681,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/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3474c04bdf..1dbacbe064 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9505,46 +9505,87 @@
   proname => 'jsonb_path_match_opr', prorettype => 'bool',
   proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_match_opr' },
 
-# txid
+# historical int8/txid_snapshot variants of xid8 functions
 { oid => '2939', descr => 'I/O',
   proname => 'txid_snapshot_in', prorettype => 'txid_snapshot',
-  proargtypes => 'cstring', prosrc => 'txid_snapshot_in' },
+  proargtypes => 'cstring', prosrc => 'xid8_snapshot_in' },
 { oid => '2940', descr => 'I/O',
   proname => 'txid_snapshot_out', prorettype => 'cstring',
-  proargtypes => 'txid_snapshot', prosrc => 'txid_snapshot_out' },
+  proargtypes => 'txid_snapshot', prosrc => 'xid8_snapshot_out' },
 { oid => '2941', descr => 'I/O',
   proname => 'txid_snapshot_recv', prorettype => 'txid_snapshot',
-  proargtypes => 'internal', prosrc => 'txid_snapshot_recv' },
+  proargtypes => 'internal', prosrc => 'xid8_snapshot_recv' },
 { oid => '2942', descr => 'I/O',
   proname => 'txid_snapshot_send', prorettype => 'bytea',
-  proargtypes => 'txid_snapshot', prosrc => 'txid_snapshot_send' },
+  proargtypes => 'txid_snapshot', prosrc => 'xid8_snapshot_send' },
 { oid => '2943', descr => 'get current transaction ID',
   proname => 'txid_current', provolatile => 's', proparallel => 'u',
-  prorettype => 'int8', proargtypes => '', prosrc => 'txid_current' },
+  prorettype => 'int8', proargtypes => '', prosrc => 'xid8_current' },
 { oid => '3348', descr => 'get current transaction ID',
   proname => 'txid_current_if_assigned', provolatile => 's', proparallel => 'u',
   prorettype => 'int8', proargtypes => '',
-  prosrc => 'txid_current_if_assigned' },
+  prosrc => 'xid8_current_if_assigned' },
 { oid => '2944', descr => 'get current snapshot',
   proname => 'txid_current_snapshot', provolatile => 's',
   prorettype => 'txid_snapshot', proargtypes => '',
-  prosrc => 'txid_current_snapshot' },
+  prosrc => 'xid8_current_snapshot' },
 { oid => '2945', descr => 'get xmin of snapshot',
   proname => 'txid_snapshot_xmin', prorettype => 'int8',
-  proargtypes => 'txid_snapshot', prosrc => 'txid_snapshot_xmin' },
+  proargtypes => 'txid_snapshot', prosrc => 'xid8_snapshot_xmin' },
 { oid => '2946', descr => 'get xmax of snapshot',
   proname => 'txid_snapshot_xmax', prorettype => 'int8',
-  proargtypes => 'txid_snapshot', prosrc => 'txid_snapshot_xmax' },
+  proargtypes => 'txid_snapshot', prosrc => 'xid8_snapshot_xmax' },
 { oid => '2947', descr => 'get set of in-progress txids in snapshot',
   proname => 'txid_snapshot_xip', prorows => '50', proretset => 't',
   prorettype => 'int8', proargtypes => 'txid_snapshot',
-  prosrc => 'txid_snapshot_xip' },
+  prosrc => 'xid8_snapshot_xip' },
 { oid => '2948', descr => 'is txid visible in snapshot?',
   proname => 'txid_visible_in_snapshot', prorettype => 'bool',
-  proargtypes => 'int8 txid_snapshot', prosrc => 'txid_visible_in_snapshot' },
+  proargtypes => 'int8 txid_snapshot', prosrc => 'xid8_visible_in_snapshot' },
 { oid => '3360', descr => 'commit status of transaction',
   proname => 'txid_status', provolatile => 'v', prorettype => 'text',
-  proargtypes => 'int8', prosrc => 'txid_status' },
+  proargtypes => 'int8', prosrc => 'xid8_status' },
+
+# xid8 functions
+{ oid => '9247', descr => 'I/O',
+  proname => 'xid8_snapshot_in', prorettype => 'xid8_snapshot',
+  proargtypes => 'cstring', prosrc => 'xid8_snapshot_in' },
+{ oid => '9248', descr => 'I/O',
+  proname => 'xid8_snapshot_out', prorettype => 'cstring',
+  proargtypes => 'xid8_snapshot', prosrc => 'xid8_snapshot_out' },
+{ oid => '9249', descr => 'I/O',
+  proname => 'xid8_snapshot_recv', prorettype => 'xid8_snapshot',
+  proargtypes => 'internal', prosrc => 'xid8_snapshot_recv' },
+{ oid => '9250', descr => 'I/O',
+  proname => 'xid8_snapshot_send', prorettype => 'bytea',
+  proargtypes => 'xid8_snapshot', prosrc => 'xid8_snapshot_send' },
+{ oid => '9251', descr => 'get current transaction ID',
+  proname => 'xid8_current', provolatile => 's', proparallel => 'u',
+  prorettype => 'xid8', proargtypes => '', prosrc => 'xid8_current' },
+{ oid => '9252', descr => 'get current transaction ID',
+  proname => 'xid8_current_if_assigned', provolatile => 's', proparallel => 'u',
+  prorettype => 'xid8', proargtypes => '',
+  prosrc => 'xid8_current_if_assigned' },
+{ oid => '9253', descr => 'get current snapshot',
+  proname => 'xid8_current_snapshot', provolatile => 's',
+  prorettype => 'xid8_snapshot', proargtypes => '',
+  prosrc => 'xid8_current_snapshot' },
+{ oid => '9254', descr => 'get xmin of snapshot',
+  proname => 'xid8_snapshot_xmin', prorettype => 'xid8',
+  proargtypes => 'xid8_snapshot', prosrc => 'xid8_snapshot_xmin' },
+{ oid => '9255', descr => 'get xmax of snapshot',
+  proname => 'xid8_snapshot_xmax', prorettype => 'xid8',
+  proargtypes => 'xid8_snapshot', prosrc => 'xid8_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 => 'xid8_snapshot_xip' },
+{ oid => '9257', descr => 'is xid8 visible in snapshot?',
+  proname => 'xid8_visible_in_snapshot', prorettype => 'bool',
+  proargtypes => 'xid8 xid8_snapshot', prosrc => 'xid8_visible_in_snapshot' },
+{ oid => '9258', descr => 'commit status of transaction',
+  proname => 'xid8_status', provolatile => 'v', prorettype => 'text',
+  proargtypes => 'xid8', prosrc => 'xid8_status' },
 
 # record comparison using normal comparison rules
 { oid => '2981',
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index a1f441b8da..2f420889e2 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -460,6 +460,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 0c03afe53d..8d350ab61b 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 |       9419
          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 |        9419
           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..efbd21af7e 100644
--- a/src/test/regress/expected/txid.out
+++ b/src/test/regress/expected/txid.out
@@ -1,4 +1,7 @@
 -- txid_snapshot data type and related functions
+-- Note: these are backward-compatibility functions and types, and have been
+-- replaced by new xid8-based variants.  See xid.sql.  The txid variants will
+-- be removed in a future release.
 -- i/o
 select '12:13:'::txid_snapshot;
  txid_snapshot 
@@ -20,19 +23,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 +238,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
diff --git a/src/test/regress/expected/xid.out b/src/test/regress/expected/xid.out
index e9e1fa8259..2d24388b59 100644
--- a/src/test/regress/expected/xid.out
+++ b/src/test/regress/expected/xid.out
@@ -134,3 +134,329 @@ create table xid8_t1 (x xid8);
 create index on xid8_t1 using btree(x);
 create index on xid8_t1 using hash(x);
 drop table xid8_t1;
+-- xid8_snapshot data type and related functions
+-- Note: another set of tests similar to this exists in txid.sql, for a limited
+-- time (the relevant functions share C code)
+-- i/o
+select '12:13:'::xid8_snapshot;
+ xid8_snapshot 
+---------------
+ 12:13:
+(1 row)
+
+select '12:18:14,16'::xid8_snapshot;
+ xid8_snapshot 
+---------------
+ 12:18:14,16
+(1 row)
+
+select '12:16:14,14'::xid8_snapshot;
+ xid8_snapshot 
+---------------
+ 12:16:14
+(1 row)
+
+-- errors
+select '31:12:'::xid8_snapshot;
+ERROR:  invalid input syntax for type xid8_snapshot: "31:12:"
+LINE 1: select '31:12:'::xid8_snapshot;
+               ^
+select '0:1:'::xid8_snapshot;
+ERROR:  invalid input syntax for type xid8_snapshot: "0:1:"
+LINE 1: select '0:1:'::xid8_snapshot;
+               ^
+select '12:13:0'::xid8_snapshot;
+ERROR:  invalid input syntax for type xid8_snapshot: "12:13:0"
+LINE 1: select '12:13:0'::xid8_snapshot;
+               ^
+select '12:16:14,13'::xid8_snapshot;
+ERROR:  invalid input syntax for type xid8_snapshot: "12:16:14,13"
+LINE 1: select '12:16:14,13'::xid8_snapshot;
+               ^
+create temp table snapshot_test (
+	nr	integer,
+	snap	xid8_snapshot
+);
+insert into snapshot_test values (1, '12:13:');
+insert into snapshot_test values (2, '12:20:13,15,18');
+insert into snapshot_test values (3, '100001:100009:100005,100007,100008');
+insert into snapshot_test values (4, '100:150:101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131');
+select snap from snapshot_test order by nr;
+                                                                snap                                                                 
+-------------------------------------------------------------------------------------------------------------------------------------
+ 12:13:
+ 12:20:13,15,18
+ 100001:100009:100005,100007,100008
+ 100:150:101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131
+(4 rows)
+
+select  xid8_snapshot_xmin(snap),
+	xid8_snapshot_xmax(snap),
+	xid8_snapshot_xip(snap)
+from snapshot_test order by nr;
+ xid8_snapshot_xmin | xid8_snapshot_xmax | xid8_snapshot_xip 
+--------------------+--------------------+-------------------
+                 12 |                 20 |                13
+                 12 |                 20 |                15
+                 12 |                 20 |                18
+             100001 |             100009 |            100005
+             100001 |             100009 |            100007
+             100001 |             100009 |            100008
+                100 |                150 |               101
+                100 |                150 |               102
+                100 |                150 |               103
+                100 |                150 |               104
+                100 |                150 |               105
+                100 |                150 |               106
+                100 |                150 |               107
+                100 |                150 |               108
+                100 |                150 |               109
+                100 |                150 |               110
+                100 |                150 |               111
+                100 |                150 |               112
+                100 |                150 |               113
+                100 |                150 |               114
+                100 |                150 |               115
+                100 |                150 |               116
+                100 |                150 |               117
+                100 |                150 |               118
+                100 |                150 |               119
+                100 |                150 |               120
+                100 |                150 |               121
+                100 |                150 |               122
+                100 |                150 |               123
+                100 |                150 |               124
+                100 |                150 |               125
+                100 |                150 |               126
+                100 |                150 |               127
+                100 |                150 |               128
+                100 |                150 |               129
+                100 |                150 |               130
+                100 |                150 |               131
+(37 rows)
+
+select id, xid8_visible_in_snapshot(id::text::xid8, snap)
+from snapshot_test, generate_series(11, 21) id
+where nr = 2;
+ id | xid8_visible_in_snapshot 
+----+--------------------------
+ 11 | t
+ 12 | t
+ 13 | f
+ 14 | t
+ 15 | f
+ 16 | t
+ 17 | t
+ 18 | f
+ 19 | t
+ 20 | f
+ 21 | f
+(11 rows)
+
+-- test bsearch
+select id, xid8_visible_in_snapshot(id::text::xid8, snap)
+from snapshot_test, generate_series(90, 160) id
+where nr = 4;
+ id  | xid8_visible_in_snapshot 
+-----+--------------------------
+  90 | t
+  91 | t
+  92 | t
+  93 | t
+  94 | t
+  95 | t
+  96 | t
+  97 | t
+  98 | t
+  99 | t
+ 100 | t
+ 101 | f
+ 102 | f
+ 103 | f
+ 104 | f
+ 105 | f
+ 106 | f
+ 107 | f
+ 108 | f
+ 109 | f
+ 110 | f
+ 111 | f
+ 112 | f
+ 113 | f
+ 114 | f
+ 115 | f
+ 116 | f
+ 117 | f
+ 118 | f
+ 119 | f
+ 120 | f
+ 121 | f
+ 122 | f
+ 123 | f
+ 124 | f
+ 125 | f
+ 126 | f
+ 127 | f
+ 128 | f
+ 129 | f
+ 130 | f
+ 131 | f
+ 132 | t
+ 133 | t
+ 134 | t
+ 135 | t
+ 136 | t
+ 137 | t
+ 138 | t
+ 139 | t
+ 140 | t
+ 141 | t
+ 142 | t
+ 143 | t
+ 144 | t
+ 145 | t
+ 146 | t
+ 147 | t
+ 148 | t
+ 149 | t
+ 150 | f
+ 151 | f
+ 152 | f
+ 153 | f
+ 154 | f
+ 155 | f
+ 156 | f
+ 157 | f
+ 158 | f
+ 159 | f
+ 160 | f
+(71 rows)
+
+-- test current values also
+select xid8_current() >= xid8_snapshot_xmin(xid8_current_snapshot());
+ ?column? 
+----------
+ t
+(1 row)
+
+-- we can't assume current is always less than xmax, however
+select xid8_visible_in_snapshot(xid8_current(), xid8_current_snapshot());
+ xid8_visible_in_snapshot 
+--------------------------
+ f
+(1 row)
+
+-- test 64bitness
+select xid8_snapshot '1000100010001000:1000100010001100:1000100010001012,1000100010001013';
+                            xid8_snapshot                            
+---------------------------------------------------------------------
+ 1000100010001000:1000100010001100:1000100010001012,1000100010001013
+(1 row)
+
+select xid8_visible_in_snapshot('1000100010001012', '1000100010001000:1000100010001100:1000100010001012,1000100010001013');
+ xid8_visible_in_snapshot 
+--------------------------
+ f
+(1 row)
+
+select xid8_visible_in_snapshot('1000100010001015', '1000100010001000:1000100010001100:1000100010001012,1000100010001013');
+ xid8_visible_in_snapshot 
+--------------------------
+ t
+(1 row)
+
+-- test 64bit overflow
+SELECT xid8_snapshot '1:9223372036854775807:3';
+      xid8_snapshot      
+-------------------------
+ 1:9223372036854775807:3
+(1 row)
+
+SELECT xid8_snapshot '1:9223372036854775808:3';
+ERROR:  invalid input syntax for type xid8_snapshot: "1:9223372036854775808:3"
+LINE 1: SELECT xid8_snapshot '1:9223372036854775808:3';
+                             ^
+-- test xid8_current_if_assigned
+BEGIN;
+SELECT xid8_current_if_assigned() IS NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT xid8_current() \gset
+SELECT xid8_current_if_assigned() IS NOT DISTINCT FROM xid8 :'xid8_current';
+ ?column? 
+----------
+ t
+(1 row)
+
+COMMIT;
+-- test xid status functions
+BEGIN;
+SELECT xid8_current() AS committed \gset
+COMMIT;
+BEGIN;
+SELECT xid8_current() AS rolledback \gset
+ROLLBACK;
+BEGIN;
+SELECT xid8_current() AS inprogress \gset
+SELECT xid8_status(:committed::text::xid8) AS committed;
+ committed 
+-----------
+ committed
+(1 row)
+
+SELECT xid8_status(:rolledback::text::xid8) AS rolledback;
+ rolledback 
+------------
+ aborted
+(1 row)
+
+SELECT xid8_status(:inprogress::text::xid8) AS inprogress;
+ inprogress  
+-------------
+ in progress
+(1 row)
+
+SELECT xid8_status('1'::xid8); -- BootstrapTransactionId is always committed
+ xid8_status 
+-------------
+ committed
+(1 row)
+
+SELECT xid8_status('2'::xid8); -- FrozenTransactionId is always committed
+ xid8_status 
+-------------
+ committed
+(1 row)
+
+SELECT xid8_status('3'::xid8); -- in regress testing FirstNormalTransactionId will always be behind oldestXmin
+ xid8_status 
+-------------
+ 
+(1 row)
+
+COMMIT;
+BEGIN;
+CREATE FUNCTION test_future_xid_status(xid8)
+RETURNS void
+LANGUAGE plpgsql
+AS
+$$
+BEGIN
+  PERFORM xid8_status($1);
+  RAISE EXCEPTION 'didn''t ERROR at xid in the future as expected';
+EXCEPTION
+  WHEN invalid_parameter_value THEN
+    RAISE NOTICE 'Got expected error for xid in the future';
+END;
+$$;
+SELECT test_future_xid_status((:inprogress + 10000)::text::xid8);
+NOTICE:  Got expected error for xid in the future
+ test_future_xid_status 
+------------------------
+ 
+(1 row)
+
+ROLLBACK;
diff --git a/src/test/regress/sql/txid.sql b/src/test/regress/sql/txid.sql
index bd6decf0ef..8d5ac98a89 100644
--- a/src/test/regress/sql/txid.sql
+++ b/src/test/regress/sql/txid.sql
@@ -1,4 +1,7 @@
 -- txid_snapshot data type and related functions
+-- Note: these are backward-compatibility functions and types, and have been
+-- replaced by new xid8-based variants.  See xid.sql.  The txid variants will
+-- be removed in a future release.
 
 -- i/o
 select '12:13:'::txid_snapshot;
diff --git a/src/test/regress/sql/xid.sql b/src/test/regress/sql/xid.sql
index a4fbca5176..8c098906e9 100644
--- a/src/test/regress/sql/xid.sql
+++ b/src/test/regress/sql/xid.sql
@@ -46,3 +46,107 @@ create table xid8_t1 (x xid8);
 create index on xid8_t1 using btree(x);
 create index on xid8_t1 using hash(x);
 drop table xid8_t1;
+
+
+-- xid8_snapshot data type and related functions
+
+-- Note: another set of tests similar to this exists in txid.sql, for a limited
+-- time (the relevant functions share C code)
+
+-- i/o
+select '12:13:'::xid8_snapshot;
+select '12:18:14,16'::xid8_snapshot;
+select '12:16:14,14'::xid8_snapshot;
+
+-- errors
+select '31:12:'::xid8_snapshot;
+select '0:1:'::xid8_snapshot;
+select '12:13:0'::xid8_snapshot;
+select '12:16:14,13'::xid8_snapshot;
+
+create temp table snapshot_test (
+	nr	integer,
+	snap	xid8_snapshot
+);
+
+insert into snapshot_test values (1, '12:13:');
+insert into snapshot_test values (2, '12:20:13,15,18');
+insert into snapshot_test values (3, '100001:100009:100005,100007,100008');
+insert into snapshot_test values (4, '100:150:101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131');
+select snap from snapshot_test order by nr;
+
+select  xid8_snapshot_xmin(snap),
+	xid8_snapshot_xmax(snap),
+	xid8_snapshot_xip(snap)
+from snapshot_test order by nr;
+
+select id, xid8_visible_in_snapshot(id::text::xid8, snap)
+from snapshot_test, generate_series(11, 21) id
+where nr = 2;
+
+-- test bsearch
+select id, xid8_visible_in_snapshot(id::text::xid8, snap)
+from snapshot_test, generate_series(90, 160) id
+where nr = 4;
+
+-- test current values also
+select xid8_current() >= xid8_snapshot_xmin(xid8_current_snapshot());
+
+-- we can't assume current is always less than xmax, however
+
+select xid8_visible_in_snapshot(xid8_current(), xid8_current_snapshot());
+
+-- test 64bitness
+
+select xid8_snapshot '1000100010001000:1000100010001100:1000100010001012,1000100010001013';
+select xid8_visible_in_snapshot('1000100010001012', '1000100010001000:1000100010001100:1000100010001012,1000100010001013');
+select xid8_visible_in_snapshot('1000100010001015', '1000100010001000:1000100010001100:1000100010001012,1000100010001013');
+
+-- test 64bit overflow
+SELECT xid8_snapshot '1:9223372036854775807:3';
+SELECT xid8_snapshot '1:9223372036854775808:3';
+
+-- test xid8_current_if_assigned
+BEGIN;
+SELECT xid8_current_if_assigned() IS NULL;
+SELECT xid8_current() \gset
+SELECT xid8_current_if_assigned() IS NOT DISTINCT FROM xid8 :'xid8_current';
+COMMIT;
+
+-- test xid status functions
+BEGIN;
+SELECT xid8_current() AS committed \gset
+COMMIT;
+
+BEGIN;
+SELECT xid8_current() AS rolledback \gset
+ROLLBACK;
+
+BEGIN;
+SELECT xid8_current() AS inprogress \gset
+
+SELECT xid8_status(:committed::text::xid8) AS committed;
+SELECT xid8_status(:rolledback::text::xid8) AS rolledback;
+SELECT xid8_status(:inprogress::text::xid8) AS inprogress;
+SELECT xid8_status('1'::xid8); -- BootstrapTransactionId is always committed
+SELECT xid8_status('2'::xid8); -- FrozenTransactionId is always committed
+SELECT xid8_status('3'::xid8); -- in regress testing FirstNormalTransactionId will always be behind oldestXmin
+
+COMMIT;
+
+BEGIN;
+CREATE FUNCTION test_future_xid_status(xid8)
+RETURNS void
+LANGUAGE plpgsql
+AS
+$$
+BEGIN
+  PERFORM xid8_status($1);
+  RAISE EXCEPTION 'didn''t ERROR at xid in the future as expected';
+EXCEPTION
+  WHEN invalid_parameter_value THEN
+    RAISE NOTICE 'Got expected error for xid in the future';
+END;
+$$;
+SELECT test_future_xid_status((:inprogress + 10000)::text::xid8);
+ROLLBACK;
-- 
2.20.1

Reply via email to