From 7d03a83f4d0736ba869fa6f93973f7623a27038a Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 19 Feb 2014 08:35:23 -0500
Subject: [PATCH] Add a pg_lsn data type, to represent an LSN.

Robert Haas and Michael Paquier
---
 doc/src/sgml/datatype.sgml           |  32 +++++
 src/backend/utils/adt/Makefile       |   2 +-
 src/backend/utils/adt/pg_lsn.c       | 180 +++++++++++++++++++++++++++
 src/include/catalog/catversion.h     |   2 +-
 src/include/catalog/pg_operator.h    |  16 +++
 src/include/catalog/pg_proc.h        |  17 +++
 src/include/catalog/pg_type.h        |   5 +
 src/include/fmgr.h                   |   2 +
 src/include/postgres.h               |  14 +++
 src/include/utils/pg_lsn.h           |  34 +++++
 src/test/regress/expected/pg_lsn.out |  62 +++++++++
 src/test/regress/parallel_schedule   |   2 +-
 src/test/regress/serial_schedule     |   1 +
 src/test/regress/sql/pg_lsn.sql      |  25 ++++
 14 files changed, 391 insertions(+), 3 deletions(-)
 create mode 100644 src/backend/utils/adt/pg_lsn.c
 create mode 100644 src/include/utils/pg_lsn.h
 create mode 100644 src/test/regress/expected/pg_lsn.out
 create mode 100644 src/test/regress/sql/pg_lsn.sql

diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 30fd9bb598d..00ccbe1bb5f 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -180,6 +180,12 @@
        <entry>geometric path on a plane</entry>
       </row>
 
+      <row>
+       <entry><type>pg_lsn</type></entry>
+       <entry></entry>
+       <entry><productname>PostgreSQL</productname> Log Sequence Number</entry>
+      </row>
+
       <row>
        <entry><type>point</type></entry>
        <entry></entry>
@@ -4504,6 +4510,32 @@ SELECT * FROM pg_attribute
    </para>
   </sect1>
 
+  <sect1 id="datatype-pg-lsn">
+   <title><acronym>pg_lsn Type</acronym></title>
+
+   <indexterm zone="datatype-pg-lsn">
+    <primary>pg_lsn</primary>
+   </indexterm>
+
+   <para>
+    The <type>pg_lsn</type> data type can be used to store LSN (Log Sequence
+    Number) data which is a pointer to a location in the XLOG. This type is a
+    representation of XLogRecPtr and an internal system type of
+    <productname>PostgreSQL</productname>.
+   </para>
+
+   <para>
+    Internally, an LSN is a 64-bit integer, representing a byte position in
+    the write-ahead log stream.  It is printed as two hexadecimal numbers of
+    up to 8 digits each, separated by a slash; for example,
+    <literal>16/B374D848</>.  The <type>pg_lsn</type> type supports the
+    standard comparison operators, like <literal>=</literal> and 
+    <literal>&gt;</literal>.  Two LSNs can be subtracted using the
+    <literal>-</literal> operator; the result is the number of bytes separating
+    those write-ahead log positions.
+   </para>
+  </sect1>
+
   <sect1 id="datatype-pseudo">
    <title>Pseudo-Types</title>
 
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 51355754d5a..644687954b2 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -24,7 +24,7 @@ OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
 	int8.o json.o jsonfuncs.o like.o \
 	lockfuncs.o mac.o misc.o nabstime.o name.o network.o numeric.o \
 	numutils.o oid.o oracle_compat.o orderedsetaggs.o \
-	pg_lzcompress.o pg_locale.o pgstatfuncs.o \
+	pg_lzcompress.o pg_locale.o pg_lsn.o pgstatfuncs.o \
 	pseudotypes.o quote.o rangetypes.o rangetypes_gist.o \
 	rangetypes_selfuncs.o rangetypes_spgist.o rangetypes_typanalyze.o \
 	regexp.o regproc.o ri_triggers.o rowtypes.o ruleutils.o \
diff --git a/src/backend/utils/adt/pg_lsn.c b/src/backend/utils/adt/pg_lsn.c
new file mode 100644
index 00000000000..39e22931362
--- /dev/null
+++ b/src/backend/utils/adt/pg_lsn.c
@@ -0,0 +1,180 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_lsn.c
+ *	  Internal PostgreSQL LSN operations
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/pg_lsn.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "libpq/pqformat.h"
+#include "utils/builtins.h"
+#include "utils/pg_lsn.h"
+
+#define MAXPG_LSNLEN			17
+#define MAXPG_LSNCOMPONENT	8
+
+/*----------------------------------------------------------
+ * Formatting and conversion routines.
+ *---------------------------------------------------------*/
+
+Datum
+pg_lsn_in(PG_FUNCTION_ARGS)
+{
+	char	   *str = PG_GETARG_CSTRING(0);
+	int			len1, len2;
+	uint32		id, off;
+	XLogRecPtr	result;
+
+	/* Sanity check input format. */
+	len1 = strspn(str, "0123456789abcdefABCDEF");
+	if (len1 < 1 || len1 > MAXPG_LSNCOMPONENT || str[len1] != '/')
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("invalid input syntax for transaction log location: \"%s\"", str)));
+	len2 = strspn(str + len1 + 1, "0123456789abcdefABCDEF");
+	if (len2 < 1 || len2 > MAXPG_LSNCOMPONENT || str[len1 + 1 + len2] != '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("invalid input syntax for transaction log location: \"%s\"", str)));
+
+	/* Decode result. */
+	id = (uint32) strtoul(str, NULL, 16);
+	off = (uint32) strtoul(str + len1 + 1, NULL, 16);
+	result = (XLogRecPtr) ((uint64) id << 32) | off;
+
+	PG_RETURN_PG_LSN(result);
+}
+
+Datum
+pg_lsn_out(PG_FUNCTION_ARGS)
+{
+	XLogRecPtr	lsn = (XLogRecPtr) PG_GETARG_PG_LSN(0);
+	char		buf[MAXPG_LSNLEN + 1];
+	char	   *result;
+	uint32		id, off;
+
+	/* Decode ID and offset */
+	id = (uint32) (lsn >> 32);
+	off = (uint32) lsn;
+
+	snprintf(buf, sizeof buf, "%X/%X", id, off);
+	result = pstrdup(buf);
+	PG_RETURN_CSTRING(result);
+}
+
+Datum
+pg_lsn_recv(PG_FUNCTION_ARGS)
+{
+	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
+	XLogRecPtr	result;
+
+	result = pq_getmsgint64(buf);
+	PG_RETURN_PG_LSN(result);
+}
+
+Datum
+pg_lsn_send(PG_FUNCTION_ARGS)
+{
+	XLogRecPtr lsn = (XLogRecPtr) PG_GETARG_PG_LSN(0);
+	StringInfoData buf;
+
+	pq_begintypsend(&buf);
+	pq_sendint64(&buf, lsn);
+	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+}
+
+
+/*----------------------------------------------------------
+ *	Operators for PostgreSQL LSNs
+ *---------------------------------------------------------*/
+
+Datum
+pg_lsn_eq(PG_FUNCTION_ARGS)
+{
+	XLogRecPtr lsn1 = (XLogRecPtr) PG_GETARG_PG_LSN(0);
+	XLogRecPtr lsn2 = (XLogRecPtr) PG_GETARG_PG_LSN(1);
+
+	PG_RETURN_BOOL(lsn1 == lsn2);
+}
+
+Datum
+pg_lsn_ne(PG_FUNCTION_ARGS)
+{
+	XLogRecPtr lsn1 = (XLogRecPtr) PG_GETARG_PG_LSN(0);
+	XLogRecPtr lsn2 = (XLogRecPtr) PG_GETARG_PG_LSN(1);
+
+	PG_RETURN_BOOL(lsn1 != lsn2);
+}
+
+Datum
+pg_lsn_lt(PG_FUNCTION_ARGS)
+{
+	XLogRecPtr lsn1 = (XLogRecPtr) PG_GETARG_PG_LSN(0);
+	XLogRecPtr lsn2 = (XLogRecPtr) PG_GETARG_PG_LSN(1);
+
+	PG_RETURN_BOOL(lsn1 < lsn2);
+}
+
+Datum
+pg_lsn_gt(PG_FUNCTION_ARGS)
+{
+	XLogRecPtr lsn1 = (XLogRecPtr) PG_GETARG_PG_LSN(0);
+	XLogRecPtr lsn2 = (XLogRecPtr) PG_GETARG_PG_LSN(1);
+
+	PG_RETURN_BOOL(lsn1 > lsn2);
+}
+
+Datum
+pg_lsn_le(PG_FUNCTION_ARGS)
+{
+	XLogRecPtr lsn1 = (XLogRecPtr) PG_GETARG_PG_LSN(0);
+	XLogRecPtr lsn2 = (XLogRecPtr) PG_GETARG_PG_LSN(1);
+
+	PG_RETURN_BOOL(lsn1 <= lsn2);
+}
+
+Datum
+pg_lsn_ge(PG_FUNCTION_ARGS)
+{
+	XLogRecPtr lsn1 = (XLogRecPtr) PG_GETARG_PG_LSN(0);
+	XLogRecPtr lsn2 = (XLogRecPtr) PG_GETARG_PG_LSN(1);
+
+	PG_RETURN_BOOL(lsn1 >= lsn2);
+}
+
+
+/*----------------------------------------------------------
+ *	Arithmetic operators on PostgreSQL LSNs.
+ *---------------------------------------------------------*/
+
+Datum
+pg_lsn_mi(PG_FUNCTION_ARGS)
+{
+	XLogRecPtr lsn1 = (XLogRecPtr) PG_GETARG_PG_LSN(0);
+	XLogRecPtr lsn2 = (XLogRecPtr) PG_GETARG_PG_LSN(1);
+	char		buf[256];
+	Datum		result;
+
+	/* Negative results are not allowed. */
+	if (lsn1 < lsn2)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("transaction log location out of range")));
+
+	/* Convert to numeric. */
+	snprintf(buf, sizeof buf, UINT64_FORMAT, lsn1 - lsn2);
+	result = DirectFunctionCall3(numeric_in,
+								 CStringGetDatum(buf),
+								 ObjectIdGetDatum(0),
+								 Int32GetDatum(-1));
+
+	return result;
+}
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 9263b0df7a0..cd1afc842df 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201402031
+#define CATALOG_VERSION_NO	201402191
 
 #endif
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 6aa48907821..e07d6d9ef97 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1592,6 +1592,22 @@ DESCR("less than or equal");
 DATA(insert OID = 2977 (  ">="	   PGNSP PGUID b f f 2950 2950 16 2976 2974 uuid_ge scalargtsel scalargtjoinsel ));
 DESCR("greater than or equal");
 
+/* pg_lsn operators */
+DATA(insert OID = 3222 (  "="	   PGNSP PGUID b f f 3220 3220 16 3222 3223 pg_lsn_eq eqsel eqjoinsel ));
+DESCR("equal");
+DATA(insert OID = 3223 (  "<>"	   PGNSP PGUID b f f 3220 3220 16 3223 3222 pg_lsn_ne neqsel neqjoinsel ));
+DESCR("not equal");
+DATA(insert OID = 3224 (  "<"	   PGNSP PGUID b f f 3220 3220 16 3225 3227 pg_lsn_lt scalarltsel scalarltjoinsel ));
+DESCR("less than");
+DATA(insert OID = 3225 (  ">"	   PGNSP PGUID b f f 3220 3220 16 3224 3226 pg_lsn_gt scalargtsel scalargtjoinsel ));
+DESCR("greater than");
+DATA(insert OID = 3226 (  "<="	   PGNSP PGUID b f f 3220 3220 16 3227 3225 pg_lsn_le scalarltsel scalarltjoinsel ));
+DESCR("less than or equal");
+DATA(insert OID = 3227 (  ">="	   PGNSP PGUID b f f 3220 3220 16 3226 3224 pg_lsn_ge scalargtsel scalargtjoinsel ));
+DESCR("greater than or equal");
+DATA(insert OID = 3228 (  "-"	   PGNSP PGUID b f f 3220 3220 1700    0	0 pg_lsn_mi - - ));
+DESCR("minus");
+
 /* enum operators */
 DATA(insert OID = 3516 (  "="	   PGNSP PGUID b t t 3500 3500 16 3516 3517 enum_eq eqsel eqjoinsel ));
 DESCR("equal");
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e6713a61065..ac726462e46 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4212,6 +4212,23 @@ DESCR("I/O");
 DATA(insert OID = 2963 (  uuid_hash		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "2950" _null_ _null_ _null_ _null_ uuid_hash _null_ _null_ _null_ ));
 DESCR("hash");
 
+/* pg_lsn */
+DATA(insert OID = 3229 (  pg_lsn_in		PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 3220 "2275" _null_ _null_ _null_ _null_ pg_lsn_in _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 3230 (  pg_lsn_out		PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "3220" _null_ _null_ _null_ _null_ pg_lsn_out _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 3231 (  pg_lsn_lt		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3220 3220" _null_ _null_ _null_ _null_ pg_lsn_lt _null_ _null_ _null_ ));
+DATA(insert OID = 3232 (  pg_lsn_le		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3220 3220" _null_ _null_ _null_ _null_ pg_lsn_le _null_ _null_ _null_ ));
+DATA(insert OID = 3233 (  pg_lsn_eq		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3220 3220" _null_ _null_ _null_ _null_ pg_lsn_eq _null_ _null_ _null_ ));
+DATA(insert OID = 3234 (  pg_lsn_ge		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3220 3220" _null_ _null_ _null_ _null_ pg_lsn_ge _null_ _null_ _null_ ));
+DATA(insert OID = 3235 (  pg_lsn_gt		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3220 3220" _null_ _null_ _null_ _null_ pg_lsn_gt _null_ _null_ _null_ ));
+DATA(insert OID = 3236 (  pg_lsn_ne		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3220 3220" _null_ _null_ _null_ _null_ pg_lsn_ne _null_ _null_ _null_ ));
+DATA(insert OID = 3237 (  pg_lsn_mi		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "3220 3220" _null_ _null_ _null_ _null_ pg_lsn_mi _null_ _null_ _null_ ));
+DATA(insert OID = 3238 (  pg_lsn_recv	PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 3220 "2281" _null_ _null_ _null_ _null_ pg_lsn_recv _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 3239 (  pg_lsn_send	PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 17 "3220" _null_ _null_ _null_ _null_ pg_lsn_send _null_ _null_ _null_ ));
+DESCR("I/O");
+
 /* enum related procs */
 DATA(insert OID = 3504 (  anyenum_in	PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 3500 "2275" _null_ _null_ _null_ _null_ anyenum_in _null_ _null_ _null_ ));
 DESCR("I/O");
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 3fc20c6a8dd..db18a23bf6e 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -577,6 +577,11 @@ DESCR("UUID datatype");
 #define UUIDOID 2950
 DATA(insert OID = 2951 ( _uuid			PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ ));
 
+/* pg_lsn */
+DATA(insert OID = 3220 ( pg_lsn			PGNSP PGUID 8 t b U t t \054 0 0 3221 pg_lsn_in pg_lsn_out pg_lsn_recv pg_lsn_send - - - d p f 0 -1 0 0 _null_ _null_ _null_ ));
+DESCR("PostgreSQL LSN datatype");
+DATA(insert OID = 3221 ( _pg_lsn			PGNSP PGUID -1 f b A f t \054 0 3220 0 array_in array_out array_recv array_send - - array_typanalyze d x f 0 -1 0 0 _null_ _null_ _null_ ));
+
 /* text search */
 DATA(insert OID = 3614 ( tsvector		PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ ));
 DESCR("text representation for text search");
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index aed81cdc26b..700435b548d 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -230,6 +230,7 @@ extern struct varlena *pg_detoast_datum_packed(struct varlena * datum);
 #define PG_GETARG_CHAR(n)	 DatumGetChar(PG_GETARG_DATUM(n))
 #define PG_GETARG_BOOL(n)	 DatumGetBool(PG_GETARG_DATUM(n))
 #define PG_GETARG_OID(n)	 DatumGetObjectId(PG_GETARG_DATUM(n))
+#define PG_GETARG_PG_LSN(n)	 DatumGetPgLsn(PG_GETARG_DATUM(n))
 #define PG_GETARG_POINTER(n) DatumGetPointer(PG_GETARG_DATUM(n))
 #define PG_GETARG_CSTRING(n) DatumGetCString(PG_GETARG_DATUM(n))
 #define PG_GETARG_NAME(n)	 DatumGetName(PG_GETARG_DATUM(n))
@@ -302,6 +303,7 @@ extern struct varlena *pg_detoast_datum_packed(struct varlena * datum);
 #define PG_RETURN_CHAR(x)	 return CharGetDatum(x)
 #define PG_RETURN_BOOL(x)	 return BoolGetDatum(x)
 #define PG_RETURN_OID(x)	 return ObjectIdGetDatum(x)
+#define PG_RETURN_PG_LSN(x)	 return PgLsnGetDatum(x)
 #define PG_RETURN_POINTER(x) return PointerGetDatum(x)
 #define PG_RETURN_CSTRING(x) return CStringGetDatum(x)
 #define PG_RETURN_NAME(x)	 return NameGetDatum(x)
diff --git a/src/include/postgres.h b/src/include/postgres.h
index a8a206d988b..e72d5fca2b1 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -483,6 +483,20 @@ typedef Datum *DatumPtr;
 
 #define ObjectIdGetDatum(X) ((Datum) SET_4_BYTES(X))
 
+/*
+ * DatumGetPgLsn
+ *		Returns PostgreSQL log sequence number of a datum.
+ */
+
+#define DatumGetPgLsn(X) ((XLogRecPtr) GET_8_BYTES(X))
+
+/*
+ * PG_LSNGetDatum
+ *		Returns datum representation for a PostgreSQL log sequence number.
+ */
+
+#define PgLsnGetDatum(X) ((Datum) SET_8_BYTES(X))
+
 /*
  * DatumGetTransactionId
  *		Returns transaction identifier value of a datum.
diff --git a/src/include/utils/pg_lsn.h b/src/include/utils/pg_lsn.h
new file mode 100644
index 00000000000..dc2193006e5
--- /dev/null
+++ b/src/include/utils/pg_lsn.h
@@ -0,0 +1,34 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_lsn.h
+ *		Declarations for operations on log sequence numbers (LSNs) of
+ *		PostgreSQL.
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/pg_lsn.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_LSN_H
+#define PG_LSN_H
+
+#include "fmgr.h"
+
+extern Datum pg_lsn_in(PG_FUNCTION_ARGS);
+extern Datum pg_lsn_out(PG_FUNCTION_ARGS);
+extern Datum pg_lsn_recv(PG_FUNCTION_ARGS);
+extern Datum pg_lsn_send(PG_FUNCTION_ARGS);
+
+extern Datum pg_lsn_eq(PG_FUNCTION_ARGS);
+extern Datum pg_lsn_ne(PG_FUNCTION_ARGS);
+extern Datum pg_lsn_lt(PG_FUNCTION_ARGS);
+extern Datum pg_lsn_gt(PG_FUNCTION_ARGS);
+extern Datum pg_lsn_le(PG_FUNCTION_ARGS);
+extern Datum pg_lsn_ge(PG_FUNCTION_ARGS);
+
+extern Datum pg_lsn_mi(PG_FUNCTION_ARGS);
+
+#endif   /* PG_LSN_H */
diff --git a/src/test/regress/expected/pg_lsn.out b/src/test/regress/expected/pg_lsn.out
new file mode 100644
index 00000000000..50258f7ef19
--- /dev/null
+++ b/src/test/regress/expected/pg_lsn.out
@@ -0,0 +1,62 @@
+--
+-- PG_LSN
+--
+CREATE TABLE PG_LSN_TBL (f1 pg_lsn);
+-- Largest and smallest input
+INSERT INTO PG_LSN_TBL VALUES ('0/0');
+INSERT INTO PG_LSN_TBL VALUES ('FFFFFFFF/FFFFFFFF');
+-- Incorrect input
+INSERT INTO PG_LSN_TBL VALUES ('G/0');
+ERROR:  invalid input syntax for transaction log location: "G/0"
+LINE 1: INSERT INTO PG_LSN_TBL VALUES ('G/0');
+                                       ^
+INSERT INTO PG_LSN_TBL VALUES ('-1/0');
+ERROR:  invalid input syntax for transaction log location: "-1/0"
+LINE 1: INSERT INTO PG_LSN_TBL VALUES ('-1/0');
+                                       ^
+INSERT INTO PG_LSN_TBL VALUES (' 0/12345678');
+ERROR:  invalid input syntax for transaction log location: " 0/12345678"
+LINE 1: INSERT INTO PG_LSN_TBL VALUES (' 0/12345678');
+                                       ^
+INSERT INTO PG_LSN_TBL VALUES ('ABCD/');
+ERROR:  invalid input syntax for transaction log location: "ABCD/"
+LINE 1: INSERT INTO PG_LSN_TBL VALUES ('ABCD/');
+                                       ^
+INSERT INTO PG_LSN_TBL VALUES ('/ABCD');
+ERROR:  invalid input syntax for transaction log location: "/ABCD"
+LINE 1: INSERT INTO PG_LSN_TBL VALUES ('/ABCD');
+                                       ^
+DROP TABLE PG_LSN_TBL;
+-- Operators
+SELECT '0/16AE7F8' = '0/16AE7F8'::pg_lsn;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '0/16AE7F8'::pg_lsn != '0/16AE7F7';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '0/16AE7F7' < '0/16AE7F8'::pg_lsn;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '0/16AE7F8' > pg_lsn '0/16AE7F7';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '0/16AE7F7'::pg_lsn - '0/16AE7F8'::pg_lsn; -- No negative results
+ERROR:  transaction log location out of range
+SELECT '0/16AE7F8'::pg_lsn - '0/16AE7F7'::pg_lsn; -- correct
+ ?column? 
+----------
+        1
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 5758b07fa42..2e3eba83dee 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -13,7 +13,7 @@ test: tablespace
 # ----------
 # The first group of parallel tests
 # ----------
-test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric txid uuid enum money rangetypes
+test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric txid uuid enum money rangetypes pg_lsn
 
 # Depends on things setup during char, varchar and text
 test: strings
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 78348f5f865..4f1dedec2b5 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -19,6 +19,7 @@ test: uuid
 test: enum
 test: money
 test: rangetypes
+test: pg_lsn
 test: strings
 test: numerology
 test: point
diff --git a/src/test/regress/sql/pg_lsn.sql b/src/test/regress/sql/pg_lsn.sql
new file mode 100644
index 00000000000..dddafb32f71
--- /dev/null
+++ b/src/test/regress/sql/pg_lsn.sql
@@ -0,0 +1,25 @@
+--
+-- PG_LSN
+--
+
+CREATE TABLE PG_LSN_TBL (f1 pg_lsn);
+
+-- Largest and smallest input
+INSERT INTO PG_LSN_TBL VALUES ('0/0');
+INSERT INTO PG_LSN_TBL VALUES ('FFFFFFFF/FFFFFFFF');
+
+-- Incorrect input
+INSERT INTO PG_LSN_TBL VALUES ('G/0');
+INSERT INTO PG_LSN_TBL VALUES ('-1/0');
+INSERT INTO PG_LSN_TBL VALUES (' 0/12345678');
+INSERT INTO PG_LSN_TBL VALUES ('ABCD/');
+INSERT INTO PG_LSN_TBL VALUES ('/ABCD');
+DROP TABLE PG_LSN_TBL;
+
+-- Operators
+SELECT '0/16AE7F8' = '0/16AE7F8'::pg_lsn;
+SELECT '0/16AE7F8'::pg_lsn != '0/16AE7F7';
+SELECT '0/16AE7F7' < '0/16AE7F8'::pg_lsn;
+SELECT '0/16AE7F8' > pg_lsn '0/16AE7F7';
+SELECT '0/16AE7F7'::pg_lsn - '0/16AE7F8'::pg_lsn; -- No negative results
+SELECT '0/16AE7F8'::pg_lsn - '0/16AE7F7'::pg_lsn; -- correct
-- 
GitLab