From e1880ad6a819b1079087a4f24fe0c59f79b68adf Mon Sep 17 00:00:00 2001
From: Thomas Munro <tmunro@postgresql.org>
Date: Tue, 28 Jan 2020 16:36:36 +1300
Subject: [PATCH 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.

Author: Thomas Munro
Reviewed-by: Fujii Masao, Takao Fujii, Yoshikazu Imai, Mark Dilger
Discussion: http://postgr.es/m/http://www.postgresql.org/message-id/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             |  11 ++
 src/include/catalog/pg_amop.dat          |  22 ++++
 src/include/catalog/pg_amproc.dat        |   6 +
 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, 458 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 d1d033178f..8a97855180 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4508,6 +4508,10 @@ INSERT INTO mytable VALUES(-1);  -- fails
     <primary>xid</primary>
    </indexterm>
 
+   <indexterm zone="datatype-oid">
+    <primary>xid8</primary>
+   </indexterm>
+
    <indexterm zone="datatype-oid">
     <primary>cid</primary>
    </indexterm>
@@ -4704,6 +4708,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 6346e65865..2f2858021c 100644
--- a/src/backend/access/hash/hashvalidate.c
+++ b/src/backend/access/hash/hashvalidate.c
@@ -313,6 +313,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..4dc9a0c67b 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -47,6 +47,7 @@
 #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 FullTransactionIdIsValid(x)		TransactionIdIsValid(XidFromFullTransactionId(x))
 #define InvalidFullTransactionId		FullTransactionIdFromEpochAndXid(0, InvalidTransactionId)
@@ -71,6 +72,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 c67768fcab..6d838792ac 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -227,6 +227,8 @@
   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' },
 
 # hash
 { amprocfamily => 'hash/bpchar_ops', amproclefttype => 'bpchar',
@@ -339,6 +341,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 6ef8b8a4e7..13389d4695 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 7c135da3b1..13d9a9fce5 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 => '9551', 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 => '9552', 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 226c904c04..50aa20e034 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 => '9553', 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 => '9559', 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 fe2c4eabb4..89d9c40e42 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 => '9560', 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 c19740e5db..306ab62720 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -790,6 +790,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.23.0

