diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 13efa9338c..10bdcff5dc 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -80,6 +80,7 @@ OBJS = \
 	rangetypes_selfuncs.o \
 	rangetypes_spgist.o \
 	rangetypes_typanalyze.o \
+	rational.o \
 	regexp.o \
 	regproc.o \
 	ri_triggers.o \
diff --git a/src/backend/utils/adt/rational.c b/src/backend/utils/adt/rational.c
new file mode 100644
index 0000000000..ab91198da3
--- /dev/null
+++ b/src/backend/utils/adt/rational.c
@@ -0,0 +1,637 @@
+/*-------------------------------------------------------------------------
+ *
+ * rational.c
+ *	  Rational number data type for the Postgres database system
+ *
+ * Copyright (c) 1998-2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/rational.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "fmgr.h"
+#include "access/hash.h"
+#include "common/int.h"			/* portable overflow detection */
+#include "libpq/pqformat.h"		/* send/recv functions */
+#include <limits.h>
+#include <math.h>
+
+PG_MODULE_MAGIC;
+
+typedef struct
+{
+	int32		numer;
+	int32		denom;
+}			Rational;
+
+static int32 gcd(int32, int32);
+static bool simplify(Rational *);
+static int32 cmp(Rational *, Rational *);
+static void neg(Rational *);
+static Rational * create(long long, long long);
+static Rational * add(Rational *, Rational *);
+static Rational * mul(Rational *, Rational *);
+static void mediant(Rational *, Rational *, Rational *);
+
+/*
+ ***************** IO ******************
+ */
+
+PG_FUNCTION_INFO_V1(rational_in);
+PG_FUNCTION_INFO_V1(rational_in_float);
+PG_FUNCTION_INFO_V1(rational_out);
+PG_FUNCTION_INFO_V1(rational_out_float);
+PG_FUNCTION_INFO_V1(rational_recv);
+PG_FUNCTION_INFO_V1(rational_create);
+PG_FUNCTION_INFO_V1(rational_in_integer);
+PG_FUNCTION_INFO_V1(rational_send);
+
+Datum
+rational_in(PG_FUNCTION_ARGS)
+{
+	char	   *s = PG_GETARG_CSTRING(0),
+			   *after;
+	long long	n,
+				d;
+
+	if (!isdigit(*s) && *s != '-')
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("Missing or invalid numerator")));
+
+	n = strtoll(s, &after, 10);
+
+	if (*after == '\0')
+	{
+		/* if just a number and no slash, interpret as an int */
+		d = 1;
+	}
+	else
+	{
+		/* otherwise look for denominator */
+		if (*after != '/')
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					 errmsg("Expecting '/' after number but found '%c'", *after)));
+		if (*(++after) == '\0')
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					 errmsg("Expecting value after '/' but got '\\0'")));
+
+		d = strtoll(after, &after, 10);
+		if (*after != '\0')
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					 errmsg("Expecting '\\0' but found '%c'", *after)));
+	}
+	PG_RETURN_POINTER(create(n, d));
+}
+
+/*
+  This function taken from John Kennedy's paper, "Algorithm To Convert a
+  Decimal to a Fraction." Translated from Pascal.
+*/
+Datum
+rational_in_float(PG_FUNCTION_ARGS)
+{
+	float8		target = PG_GETARG_FLOAT8(0),
+				z,
+				fnumer,
+				fdenom,
+				error;
+	int32		prev_denom,
+				sign;
+	Rational   *result = palloc(sizeof(Rational));
+
+	if (target == (int32) target)
+	{
+		result->numer = (int32) target;
+		result->denom = 1;
+		PG_RETURN_POINTER(result);
+	}
+
+	sign = target < 0.0 ? -1 : 1;
+	target = fabs(target);
+
+	if (!(target <= INT32_MAX))
+	{							/* also excludes NaN's */
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("value too large for rational")));
+	}
+	z = target;
+	prev_denom = 0;
+	result->numer = (int32) round(target);
+	result->denom = 1;
+	do
+	{
+		z = 1.0 / (z - floor(z));
+		fdenom = result->denom * floor(z) + prev_denom;
+		fnumer = round(target * fdenom);
+		if (fnumer > INT32_MAX || fdenom > INT32_MAX)
+			break;
+		prev_denom = result->denom;
+		result->numer = (int32) fnumer;
+		result->denom = (int32) fdenom;
+
+		error = fabs(target - ((float8) result->numer / (float8) result->denom));
+	} while (z != floor(z) && error >= 1e-12);
+
+	result->numer *= sign;
+	PG_RETURN_POINTER(result);
+}
+
+Datum
+rational_out(PG_FUNCTION_ARGS)
+{
+	Rational   *r = (Rational *) PG_GETARG_POINTER(0);
+
+	PG_RETURN_CSTRING(psprintf("%d/%d", r->numer, r->denom));
+}
+
+Datum
+rational_out_float(PG_FUNCTION_ARGS)
+{
+	Rational   *r = (Rational *) PG_GETARG_POINTER(0);
+
+	PG_RETURN_FLOAT8((float8) r->numer / (float8) r->denom);
+}
+
+Datum
+rational_recv(PG_FUNCTION_ARGS)
+{
+	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
+
+	PG_RETURN_POINTER(create(pq_getmsgint(buf, sizeof(int32)),
+							 pq_getmsgint(buf, sizeof(int32))));
+}
+
+Datum
+rational_create(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(create(PG_GETARG_INT32(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+rational_in_integer(PG_FUNCTION_ARGS)
+{
+	int32		n = PG_GETARG_INT32(0);
+	Rational   *result = palloc(sizeof(Rational));
+
+	result->numer = n;
+	result->denom = 1;
+
+	PG_RETURN_POINTER(result);
+}
+
+Datum
+rational_send(PG_FUNCTION_ARGS)
+{
+	Rational   *r = (Rational *) PG_GETARG_POINTER(0);
+	StringInfoData buf;
+
+	pq_begintypsend(&buf);
+	pq_sendint(&buf, r->numer, sizeof(int32));
+	pq_sendint(&buf, r->denom, sizeof(int32));
+
+	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+}
+
+/*
+ ************* ARITHMETIC **************
+ */
+
+PG_FUNCTION_INFO_V1(rational_simplify);
+PG_FUNCTION_INFO_V1(rational_add);
+PG_FUNCTION_INFO_V1(rational_sub);
+PG_FUNCTION_INFO_V1(rational_mul);
+PG_FUNCTION_INFO_V1(rational_div);
+PG_FUNCTION_INFO_V1(rational_neg);
+
+Datum
+rational_simplify(PG_FUNCTION_ARGS)
+{
+	Rational   *in = (Rational *) PG_GETARG_POINTER(0);
+	Rational   *out = palloc(sizeof(Rational));
+
+	memcpy(out, in, sizeof(Rational));
+	simplify(out);
+
+	PG_RETURN_POINTER(out);
+}
+
+Datum
+rational_add(PG_FUNCTION_ARGS)
+{
+	Rational	x,
+				y;
+
+	memcpy(&x, PG_GETARG_POINTER(0), sizeof(Rational));
+	memcpy(&y, PG_GETARG_POINTER(1), sizeof(Rational));
+
+	PG_RETURN_POINTER(add(&x, &y));
+}
+
+Datum
+rational_sub(PG_FUNCTION_ARGS)
+{
+	Rational	x,
+				y;
+
+	memcpy(&x, PG_GETARG_POINTER(0), sizeof(Rational));
+	memcpy(&y, PG_GETARG_POINTER(1), sizeof(Rational));
+
+	neg(&y);
+	PG_RETURN_POINTER(add(&x, &y));
+}
+
+Datum
+rational_mul(PG_FUNCTION_ARGS)
+{
+	Rational	x,
+				y;
+
+	memcpy(&x, PG_GETARG_POINTER(0), sizeof(Rational));
+	memcpy(&y, PG_GETARG_POINTER(1), sizeof(Rational));
+
+	PG_RETURN_POINTER(mul(&x, &y));
+}
+
+Datum
+rational_div(PG_FUNCTION_ARGS)
+{
+	Rational	x,
+				y;
+	int32		tmp;
+
+	memcpy(&x, PG_GETARG_POINTER(0), sizeof(Rational));
+	memcpy(&y, PG_GETARG_POINTER(1), sizeof(Rational));
+	tmp = y.numer;
+	y.numer = y.denom;
+	y.denom = tmp;
+
+	PG_RETURN_POINTER(mul(&x, &y));
+}
+
+Datum
+rational_neg(PG_FUNCTION_ARGS)
+{
+	Rational   *out = palloc(sizeof(Rational));
+
+	memcpy(out, PG_GETARG_POINTER(0), sizeof(Rational));
+	neg(out);
+
+	PG_RETURN_POINTER(out);
+}
+
+/*
+ *************** UTILITY ***************
+ */
+
+PG_FUNCTION_INFO_V1(rational_hash);
+PG_FUNCTION_INFO_V1(rational_intermediate);
+
+Datum
+rational_hash(PG_FUNCTION_ARGS)
+{
+	Rational	x;
+
+	memcpy(&x, PG_GETARG_POINTER(0), sizeof(Rational));
+
+	/*
+	 * hash_any works at binary level, so we must simplify fraction
+	 */
+	simplify(&x);
+
+	return hash_any((const unsigned char *) &x, sizeof(Rational));
+}
+
+Datum
+rational_intermediate(PG_FUNCTION_ARGS)
+{
+	Rational	x,
+				y,				/* arguments */
+				lo = {0, 1},
+				hi = {1, 0},	/* yes, an internal use of 1/0 */
+			   *med = palloc(sizeof(Rational));
+
+	/*
+	 * x = coalesce(lo, arg[0]) y = coalesce(hi, arg[1])
+	 */
+	memcpy(&x,
+		   PG_ARGISNULL(0) ? &lo : (Rational *) PG_GETARG_POINTER(0),
+		   sizeof(Rational));
+	memcpy(&y,
+		   PG_ARGISNULL(1) ? &hi : (Rational *) PG_GETARG_POINTER(1),
+		   sizeof(Rational));
+
+	if (cmp(&x, &lo) < 0 || cmp(&y, &lo) < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("arguments must be non-negative")));
+
+	if (cmp(&x, &y) >= 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("first argument must be strictly smaller than second")));
+
+	while (true)
+	{
+		mediant(&lo, &hi, med);
+		if (cmp(med, &x) < 1)
+			memcpy(&lo, med, sizeof(Rational));
+		else if (cmp(med, &y) > -1)
+			memcpy(&hi, med, sizeof(Rational));
+		else
+			break;
+	}
+
+	PG_RETURN_POINTER(med);
+}
+
+
+/*
+ ************* COMPARISON **************
+ */
+
+
+PG_FUNCTION_INFO_V1(rational_cmp);
+PG_FUNCTION_INFO_V1(rational_eq);
+PG_FUNCTION_INFO_V1(rational_ne);
+PG_FUNCTION_INFO_V1(rational_lt);
+PG_FUNCTION_INFO_V1(rational_le);
+PG_FUNCTION_INFO_V1(rational_gt);
+PG_FUNCTION_INFO_V1(rational_ge);
+
+PG_FUNCTION_INFO_V1(rational_smaller);
+PG_FUNCTION_INFO_V1(rational_larger);
+
+Datum
+rational_cmp(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(
+					cmp((Rational *) PG_GETARG_POINTER(0), (Rational *) PG_GETARG_POINTER(1)));
+}
+
+Datum
+rational_eq(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(
+				   cmp((Rational *) PG_GETARG_POINTER(0), (Rational *) PG_GETARG_POINTER(1)) == 0);
+}
+
+Datum
+rational_ne(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(
+				   cmp((Rational *) PG_GETARG_POINTER(0), (Rational *) PG_GETARG_POINTER(1)) != 0);
+}
+
+Datum
+rational_lt(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(
+				   cmp((Rational *) PG_GETARG_POINTER(0), (Rational *) PG_GETARG_POINTER(1)) < 0);
+}
+
+Datum
+rational_le(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(
+				   cmp((Rational *) PG_GETARG_POINTER(0), (Rational *) PG_GETARG_POINTER(1)) <= 0);
+}
+
+Datum
+rational_gt(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(
+				   cmp((Rational *) PG_GETARG_POINTER(0), (Rational *) PG_GETARG_POINTER(1)) > 0);
+}
+
+Datum
+rational_ge(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(
+				   cmp((Rational *) PG_GETARG_POINTER(0), (Rational *) PG_GETARG_POINTER(1)) >= 0);
+}
+
+Datum
+rational_smaller(PG_FUNCTION_ARGS)
+{
+	Rational   *a = (Rational *) PG_GETARG_POINTER(0),
+			   *b = (Rational *) PG_GETARG_POINTER(1);
+
+	PG_RETURN_POINTER(cmp(a, b) < 0 ? a : b);
+}
+
+Datum
+rational_larger(PG_FUNCTION_ARGS)
+{
+	Rational   *a = (Rational *) PG_GETARG_POINTER(0),
+			   *b = (Rational *) PG_GETARG_POINTER(1);
+
+	PG_RETURN_POINTER(cmp(a, b) > 0 ? a : b);
+}
+
+
+/*
+ ************** INTERNAL ***************
+ */
+
+int32
+gcd(int32 a, int32 b)
+{
+	int32		temp;
+
+	while (b != 0)
+	{
+		temp = a % b;
+		a = b;
+		b = temp;
+	}
+
+	return a;
+}
+
+bool
+simplify(Rational * r)
+{
+	int32		common = gcd(r->numer, r->denom);
+
+	/*
+	 * tricky: avoid overflow from (INT32_MIN / -1)
+	 */
+	if (common != -1 || (r->numer != INT32_MIN && r->denom != INT32_MIN))
+	{
+		r->numer /= common;
+		r->denom /= common;
+	}
+
+	/*
+	 * prevent negative denominator, but do not negate the smallest value --
+	 * that would produce overflow
+	 */
+	if (r->denom < 0 && r->numer != INT32_MIN && r->denom != INT32_MIN)
+	{
+		r->numer *= -1;
+		r->denom *= -1;
+	}
+	return (common != 1) && (common != -1);
+}
+
+int32
+cmp(Rational * a, Rational * b)
+{
+	/*
+	 * Overflow is not an option, we need a total order so that btree indices
+	 * do not die. Hence do the arithmetic in 64 bits.
+	 */
+	int64		cross1 = (int64) a->numer * (int64) b->denom,
+				cross2 = (int64) a->denom * (int64) b->numer;
+
+	return (cross1 > cross2) - (cross1 < cross2);
+}
+
+void
+neg(Rational * r)
+{
+	if (r->numer == INT32_MIN)
+	{
+		simplify(r);
+
+		/*
+		 * check again
+		 */
+		if (r->numer == INT32_MIN)
+		{
+			/*
+			 * denom can't be MIN too or fraction would have previously
+			 * simplified to 1/1
+			 */
+			r->denom *= -1;
+			return;
+		}
+	}
+	r->numer *= -1;
+}
+
+
+Rational *
+create(long long n, long long d)
+{
+	Rational   *result = palloc(sizeof(Rational));
+
+	if (d == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_DIVISION_BY_ZERO),
+				 errmsg("fraction cannot have zero denominator")));
+
+	if (n < INT32_MIN || n > INT32_MAX || d < INT32_MIN || d > INT32_MAX)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("numerator or denominator outside valid int32 value")));
+
+	/*
+	 * prevent negative denominator, but do not negate the smallest value or
+	 * else it would overflow
+	 */
+	if (d >= 0 || n == INT32_MIN || d == INT32_MIN)
+	{
+		result->numer = (int32) n;
+		result->denom = (int32) d;
+	}
+	else
+	{
+		result->numer = (int32) -n;
+		result->denom = (int32) -d;
+	}
+
+	return result;
+}
+
+Rational *
+add(Rational * x, Rational * y)
+{
+	int32		xnyd,
+				ynxd,
+				numer,
+				denom;
+	bool		nxyd_bad,
+				ynxd_bad,
+				numer_bad,
+				denom_bad;
+	Rational   *result;
+
+retry_add:
+	nxyd_bad = pg_mul_s32_overflow(x->numer, y->denom, &xnyd);
+	ynxd_bad = pg_mul_s32_overflow(y->numer, x->denom, &ynxd);
+	numer_bad = pg_add_s32_overflow(xnyd, ynxd, &numer);
+	denom_bad = pg_mul_s32_overflow(x->denom, y->denom, &denom);
+
+	if (nxyd_bad || ynxd_bad || numer_bad || denom_bad)
+	{
+		/* overflow in intermediate value */
+		if (!simplify(x) && !simplify(y))
+		{
+			/* neither fraction could reduce, cannot proceed */
+			ereport(ERROR, (
+							errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+							errmsg("intermediate value overflow in rational addition")
+							));
+		}
+		/* the fraction(s) reduced, good for one more retry */
+		goto retry_add;
+	}
+	result = palloc(sizeof(Rational));
+	result->numer = numer;
+	result->denom = denom;
+	return result;
+}
+
+Rational *
+mul(Rational * x, Rational * y)
+{
+	int32		numer,
+				denom;
+	bool		numer_bad,
+				denom_bad;
+	Rational   *result;
+
+retry_mul:
+	numer_bad = pg_mul_s32_overflow(x->numer, y->numer, &numer);
+	denom_bad = pg_mul_s32_overflow(x->denom, y->denom, &denom);
+
+	if (numer_bad || denom_bad)
+	{
+		/* overflow in intermediate value */
+		if (!simplify(x) && !simplify(y))
+		{
+			/* neither fraction could reduce, cannot proceed */
+			ereport(ERROR,
+					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+					 errmsg("intermediate value overflow in rational multiplication")));
+		}
+		/* the fraction(s) reduced, good for one more retry */
+		goto retry_mul;
+	}
+	result = palloc(sizeof(Rational));
+	result->numer = numer;
+	result->denom = denom;
+
+	return result;
+}
+
+void
+mediant(Rational * x, Rational * y, Rational * m)
+{
+	/*
+	 * Rational_intermediate sends fractions with small numers and denoms, and
+	 * slowly builds up. The search will take forever before we ever get close
+	 * to arithmetic overflow in this function, so I don't guard it here.
+	 */
+	m->numer = x->numer + y->numer;
+	m->denom = x->denom + y->denom;
+}
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..3954b2d55e 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -69,6 +69,8 @@
   aggcombinefn => 'float4pl', aggtranstype => 'float4' },
 { aggfnoid => 'sum(float8)', aggtransfn => 'float8pl',
   aggcombinefn => 'float8pl', aggtranstype => 'float8' },
+{ aggfnoid => 'sum(rational)', aggtransfn => 'rational_add',
+  aggcombinefn => 'rational_add', aggtranstype => 'rational' },
 { aggfnoid => 'sum(money)', aggtransfn => 'cash_pl', aggcombinefn => 'cash_pl',
   aggmtransfn => 'cash_pl', aggminvtransfn => 'cash_mi',
   aggtranstype => 'money', aggmtranstype => 'money' },
@@ -104,6 +106,9 @@
 { aggfnoid => 'max(float8)', aggtransfn => 'float8larger',
   aggcombinefn => 'float8larger', aggsortop => '>(float8,float8)',
   aggtranstype => 'float8' },
+{ aggfnoid => 'max(rational)', aggtransfn => 'rational_larger',
+  aggcombinefn => 'rational_larger', aggsortop => '>(rational,rational)',
+  aggtranstype => 'rational' },
 { aggfnoid => 'max(date)', aggtransfn => 'date_larger',
   aggcombinefn => 'date_larger', aggsortop => '>(date,date)',
   aggtranstype => 'date' },
@@ -169,6 +174,9 @@
 { aggfnoid => 'min(float8)', aggtransfn => 'float8smaller',
   aggcombinefn => 'float8smaller', aggsortop => '<(float8,float8)',
   aggtranstype => 'float8' },
+{ aggfnoid => 'min(rational)', aggtransfn => 'rational_smaller',
+  aggcombinefn => 'rational_smaller', aggsortop => '<(rational,rational)',
+  aggtranstype => 'rational' },
 { aggfnoid => 'min(date)', aggtransfn => 'date_smaller',
   aggcombinefn => 'date_smaller', aggsortop => '<(date,date)',
   aggtranstype => 'date' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 11aaa519c8..1377f05e64 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -281,6 +281,23 @@
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float8,float4)',
   amopmethod => 'btree' },
 
+# default operators rational
+{ amopfamily => 'btree/rational_ops', amoplefttype => 'rational',
+  amoprighttype => 'rational', amopstrategy => '1', amopopr => '<(rational,rational)',
+  amopmethod => 'btree' },
+{ amopfamily => 'btree/rational_ops', amoplefttype => 'rational',
+  amoprighttype => 'rational', amopstrategy => '2',
+  amopopr => '<=(rational,rational)', amopmethod => 'btree' },
+{ amopfamily => 'btree/rational_ops', amoplefttype => 'rational',
+  amoprighttype => 'rational', amopstrategy => '3', amopopr => '=(rational,rational)',
+  amopmethod => 'btree' },
+{ amopfamily => 'btree/rational_ops', amoplefttype => 'rational',
+  amoprighttype => 'rational', amopstrategy => '4',
+  amopopr => '>=(rational,rational)', amopmethod => 'btree' },
+{ amopfamily => 'btree/rational_ops', amoplefttype => 'rational',
+  amoprighttype => 'rational', amopstrategy => '5', amopopr => '>(rational,rational)',
+  amopmethod => 'btree' },
+
 # btree char_ops
 
 { amopfamily => 'btree/char_ops', amoplefttype => 'char',
@@ -903,6 +920,11 @@
   amoprighttype => 'float4', amopstrategy => '1', amopopr => '=(float8,float4)',
   amopmethod => 'hash' },
 
+# rational_ops
+{ amopfamily => 'hash/rational_ops', amoplefttype => 'rational',
+  amoprighttype => 'rational', amopstrategy => '1', amopopr => '=(rational,rational)',
+  amopmethod => 'hash' },
+
 # network_ops
 { amopfamily => 'hash/network_ops', amoplefttype => 'inet',
   amoprighttype => 'inet', amopstrategy => '1', amopopr => '=(inet,inet)',
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index c67768fcab..20c977301e 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -91,6 +91,11 @@
 { amprocfamily => 'btree/float_ops', amproclefttype => 'float4',
   amprocrighttype => 'float8', amprocnum => '3',
   amproc => 'in_range(float4,float4,float8,bool,bool)' },
+
+
+{ amprocfamily => 'btree/rational_ops', amproclefttype => 'rational',
+  amprocrighttype => 'rational', amprocnum => '1', amproc => 'rational_cmp' },
+
 { amprocfamily => 'btree/network_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '1', amproc => 'network_cmp' },
 { amprocfamily => 'btree/network_ops', amproclefttype => 'inet',
@@ -252,6 +257,8 @@
 { amprocfamily => 'hash/float_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '2',
   amproc => 'hashfloat4extended' },
+{ amprocfamily => 'hash/rational_ops', amproclefttype => 'rational',
+  amprocrighttype => 'rational', amprocnum => '1', amproc => 'hashrational' },
 { amprocfamily => 'hash/float_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '1', amproc => 'hashfloat8' },
 { amprocfamily => 'hash/float_ops', amproclefttype => 'float8',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 6ef8b8a4e7..21b15b0330 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -36,6 +36,14 @@
   castcontext => 'i', castmethod => 'f' },
 { castsource => 'int2', casttarget => 'float8', castfunc => 'float8(int2)',
   castcontext => 'i', castmethod => 'f' },
+
+{ castsource => 'float8', casttarget => 'rational', castfunc => 'rational_in_float',
+  castcontext => 'i', castmethod => 'f' },
+{ castsource => 'rational', casttarget => 'float8', castfunc => 'rational_out_float',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int4', casttarget => 'rational', castfunc => 'rational_in_integer',
+  castcontext => 'i', castmethod => 'f' },
+
 { castsource => 'int2', casttarget => 'numeric', castfunc => 'numeric(int2)',
   castcontext => 'i', castmethod => 'f' },
 { castsource => 'int4', casttarget => 'int8', castfunc => 'int8(int4)',
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index ab2f50c9eb..c5bae7945d 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -47,6 +47,10 @@
   opcintype => 'float4' },
 { opcmethod => 'hash', opcname => 'float4_ops', opcfamily => 'hash/float_ops',
   opcintype => 'float4' },
+{ opcmethod => 'btree', opcname => 'rational_ops', opcfamily => 'btree/rational_ops',
+  opcintype => 'rational' },
+{ opcmethod => 'hash', opcname => 'rational_ops', opcfamily => 'hash/rational_ops',
+  opcintype => 'rational' },
 { oid => '3123', oid_symbol => 'FLOAT8_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'float8_ops', opcfamily => 'btree/float_ops',
   opcintype => 'float8' },
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 7c135da3b1..5219b3bfaf 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3298,4 +3298,53 @@
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'contsel', oprjoin => 'contjoinsel' },
 
+# Rationals
+
+{ oid => '8565', descr => 'equal',
+  oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'rational',
+  oprright => 'rational', oprresult => 'bool', oprcom => '=(rational,rational)',
+  oprnegate => '<>(rational,rational)', oprcode => 'rational_eq', oprrest => 'eqsel',
+  oprjoin => 'eqjoinsel' },
+{ oid => '8566', descr => 'not equal',
+  oprname => '<>', oprleft => 'rational', oprright => 'rational',
+  oprresult => 'bool', oprcom => '<>(rational,rational)',
+  oprnegate => '=(rational,rational)', oprcode => 'rational_ne', oprrest => 'neqsel',
+  oprjoin => 'neqjoinsel' },
+{ oid => '8567', descr => 'less than',
+  oprname => '<', oprleft => 'rational', oprright => 'rational',
+  oprresult => 'bool', oprcom => '>(rational,rational)',
+  oprnegate => '>=(rational,rational)', oprcode => 'rational_lt',
+  oprrest => 'scalarltsel', oprjoin => 'scalarltjoinsel' },
+{ oid => '8568', descr => 'less than or equal',
+  oprname => '<=', oprleft => 'rational', oprright => 'rational',
+  oprresult => 'bool', oprcom => '>=(rational,rational)',
+  oprnegate => '>(rational,rational)', oprcode => 'rational_le',
+  oprrest => 'scalarlesel', oprjoin => 'scalarlejoinsel' },
+{ oid => '8569', descr => 'greater than',
+  oprname => '>', oprleft => 'rational', oprright => 'rational',
+  oprresult => 'bool', oprcom => '<(rational,rational)',
+  oprnegate => '<=(rational,rational)', oprcode => 'rational_gt',
+  oprrest => 'scalargtsel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8570', descr => 'greater than or equal',
+  oprname => '>=', oprleft => 'rational', oprright => 'rational',
+  oprresult => 'bool', oprcom => '<=(rational,rational)',
+  oprnegate => '<(rational,rational)', oprcode => 'rational_ge',
+  oprrest => 'scalargesel', oprjoin => 'scalargejoinsel' },
+
+{ oid => '8571', descr => 'add',
+  oprname => '+', oprleft => 'rational', oprright => 'rational',
+  oprresult => 'rational', oprcom => '+(rational,rational)', oprcode => 'rational_add' },
+{ oid => '8572', descr => 'negate',
+  oprname => '-', oprkind => 'l', oprleft => '0', oprright => 'rational',
+  oprresult => 'rational', oprcode => 'rational_neg' },
+{ oid => '8573', descr => 'subtract',
+  oprname => '-', oprleft => 'rational', oprright => 'rational',
+  oprresult => 'rational', oprcode => 'rational_sub' },
+{ oid => '8574', descr => 'divide',
+  oprname => '/', oprleft => 'rational', oprright => 'rational',
+  oprresult => 'rational', oprcode => 'rational_div' },
+{ oid => '8575', descr => 'multiply',
+  oprname => '*', oprleft => 'rational', oprright => 'rational',
+  oprresult => 'rational', oprcom => '*(rational,rational)',
+  oprcode => 'rational_mul' },
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 26227df216..cca4210982 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -38,6 +38,10 @@
   opfmethod => 'btree', opfname => 'float_ops' },
 { oid => '1971',
   opfmethod => 'hash', opfname => 'float_ops' },
+{ oid => '8579',
+  opfmethod => 'btree', opfname => 'rational_ops' },
+{ oid => '8580',
+  opfmethod => 'hash', opfname => 'rational_ops' },
 { oid => '1974', oid_symbol => 'NETWORK_BTREE_FAM_OID',
   opfmethod => 'btree', opfname => 'network_ops' },
 { oid => '1975',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 226c904c04..469082162a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6206,6 +6206,9 @@
 { oid => '2111', descr => 'sum as float8 across all float8 input values',
   proname => 'sum', prokind => 'a', proisstrict => 'f', prorettype => 'float8',
   proargtypes => 'float8', prosrc => 'aggregate_dummy' },
+{ oid => '8578', descr => 'sum as rational across all rational input values',
+  proname => 'sum', prokind => 'a', proisstrict => 'f', prorettype => 'rational',
+  proargtypes => 'rational', prosrc => 'aggregate_dummy' },
 { oid => '2112', descr => 'sum as money across all money input values',
   proname => 'sum', prokind => 'a', proisstrict => 'f', prorettype => 'money',
   proargtypes => 'money', prosrc => 'aggregate_dummy' },
@@ -6235,6 +6238,9 @@
 { oid => '2120', descr => 'maximum value of all float8 input values',
   proname => 'max', prokind => 'a', proisstrict => 'f', prorettype => 'float8',
   proargtypes => 'float8', prosrc => 'aggregate_dummy' },
+{ oid => '8576', descr => 'maximum value of all rational input values',
+  proname => 'max', prokind => 'a', proisstrict => 'f', prorettype => 'rational',
+  proargtypes => 'rational', prosrc => 'aggregate_dummy' },
 { oid => '2122', descr => 'maximum value of all date input values',
   proname => 'max', prokind => 'a', proisstrict => 'f', prorettype => 'date',
   proargtypes => 'date', prosrc => 'aggregate_dummy' },
@@ -6302,6 +6308,9 @@
 { oid => '2136', descr => 'minimum value of all float8 input values',
   proname => 'min', prokind => 'a', proisstrict => 'f', prorettype => 'float8',
   proargtypes => 'float8', prosrc => 'aggregate_dummy' },
+{ oid => '8577', descr => 'minimum value of all rational input values',
+  proname => 'min', prokind => 'a', proisstrict => 'f', prorettype => 'rational',
+  proargtypes => 'rational', prosrc => 'aggregate_dummy' },
 { oid => '2138', descr => 'minimum value of all date input values',
   proname => 'min', prokind => 'a', proisstrict => 'f', prorettype => 'date',
   proargtypes => 'date', prosrc => 'aggregate_dummy' },
@@ -10768,4 +10777,91 @@
   proname => 'pg_partition_root', prorettype => 'regclass',
   proargtypes => 'regclass', prosrc => 'pg_partition_root' },
 
+# rational numbers
+{ oid => '8536', descr => 'I/O',
+  proname => 'rational_in', provolatile => 's', prorettype => 'rational',
+  proargtypes => 'cstring', prosrc => 'rational_in' },
+{ oid => '8537', descr => 'I/O',
+  proname => 'rational_in_float', provolatile => 's', prorettype => 'rational',
+  proargtypes => 'float8', prosrc => 'rational_in_float' },
+{ oid => '8535', descr => 'I/O',
+  proname => 'rational_in_integer', provolatile => 's', prorettype => 'rational',
+  proargtypes => 'int4', prosrc => 'rational_in_integer' },
+{ oid => '8538', descr => 'I/O',
+  proname => 'rational_out', provolatile => 's', prorettype => 'cstring',
+  proargtypes => 'rational', prosrc => 'rational_out' },
+{ oid => '8539', descr => 'I/O',
+  proname => 'rational_out_float', provolatile => 's', prorettype => 'float8',
+  proargtypes => 'rational', prosrc => 'rational_out_float' },
+{ oid => '8540', descr => 'I/O',
+  proname => 'rational_recv', prorettype => 'rational', proargtypes => 'internal',
+  prosrc => 'rational_recv' },
+{ oid => '8541', descr => 'I/O',
+  proname => 'rational_send', prorettype => 'bytea', proargtypes => 'rational',
+  prosrc => 'rational_send' },
+
+{ oid => '8542', descr => 'Construction',
+  proname => 'rational', prorettype => 'rational',
+  proargtypes => 'int4 int4', prosrc => 'rational_create' },
+{ oid => '8543', descr => 'Construction',
+  proname => 'rational', prorettype => 'rational',
+  proargtypes => 'int4', prosrc => 'rational_in_integer' },
+
+{ oid => '8547', descr => 'Arithmetic',
+  proname => 'rational_add', prorettype => 'rational',
+  proargtypes => 'rational rational', prosrc => 'rational_add' },
+{ oid => '8548', descr => 'Arithmetic',
+  proname => 'rational_sub', prorettype => 'rational',
+  proargtypes => 'rational rational', prosrc => 'rational_sub' },
+{ oid => '8549', descr => 'Arithmetic',
+  proname => 'rational_mul', prorettype => 'rational',
+  proargtypes => 'rational rational', prosrc => 'rational_mul' },
+{ oid => '8550', descr => 'Arithmetic',
+  proname => 'rational_div', prorettype => 'rational',
+  proargtypes => 'rational rational', prosrc => 'rational_div' },
+{ oid => '8551', descr => 'Arithmetic',
+  proname => 'rational_neg', prorettype => 'rational',
+  proargtypes => 'rational', prosrc => 'rational_neg' },
+{ oid => '8552', descr => 'Manipulation',
+  proname => 'rational_simplify', prorettype => 'rational',
+  proargtypes => 'rational', prosrc => 'rational_simplify' },
+{ oid => '8553', descr => 'Manipulation',
+  proname => 'rational_intermediate', prorettype => 'rational',
+  proargtypes => 'rational rational', proisstrict => 'f',
+  prosrc => 'rational_intermediate' },
+
+{ oid => '8554', descr => 'Comparison',
+  proname => 'rational_eq', prorettype => 'bool',
+  proargtypes => 'rational rational', prosrc => 'rational_eq' },
+{ oid => '8555', descr => 'Comparison',
+  proname => 'rational_ne', prorettype => 'bool',
+  proargtypes => 'rational rational', prosrc => 'rational_ne' },
+{ oid => '8556', descr => 'Comparison',
+  proname => 'rational_lt', prorettype => 'bool',
+  proargtypes => 'rational rational', prosrc => 'rational_lt' },
+{ oid => '8557', descr => 'Comparison',
+  proname => 'rational_le', prorettype => 'bool',
+  proargtypes => 'rational rational', prosrc => 'rational_le' },
+{ oid => '8558', descr => 'Comparison',
+  proname => 'rational_gt', prorettype => 'bool',
+  proargtypes => 'rational rational', prosrc => 'rational_gt' },
+{ oid => '8559', descr => 'Comparison',
+  proname => 'rational_ge', prorettype => 'bool',
+  proargtypes => 'rational rational', prosrc => 'rational_ge' },
+{ oid => '8560', descr => 'Comparison',
+  proname => 'rational_cmp', prorettype => 'int4',
+  proargtypes => 'rational rational', prosrc => 'rational_cmp' },
+{ oid => '8561', descr => 'smaller of two',
+  proname => 'rational_smaller', prorettype => 'rational',
+  proargtypes => 'rational rational', prosrc => 'rational_smaller' },
+{ oid => '8562', descr => 'larger of two',
+  proname => 'rational_larger', prorettype => 'rational',
+  proargtypes => 'rational rational', prosrc => 'rational_larger' },
+
+{ oid => '8563', descr => 'hash',
+  proname => 'hashrational', prorettype => 'int4',
+  proargtypes => 'rational', prosrc => 'rational_hash' },
+
 ]
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index fe2c4eabb4..9fcf969d10 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -595,4 +595,10 @@
   typcategory => 'P', typinput => 'anyrange_in', typoutput => 'anyrange_out',
   typreceive => '-', typsend => '-', typalign => 'd', typstorage => 'x' },
 
+{ oid => '8564', array_type_oid => '8601',
+  descr => 'rational numbers (fractions) with int4 numerator and denominator',
+  typname => 'rational', typlen => '8', typbyval => 'f', typtype => 'b',
+  typcategory => 'N', typinput => 'rational_in', typoutput => 'rational_out',
+  typreceive => 'rational_recv', typsend => 'rational_send',
+  typalign => 'd' },
 ]
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index c19740e5db..c441ced3ed 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -168,9 +168,10 @@ WHERE p1.oid < p2.oid AND
      p1.proretset != p2.proretset OR
      p1.provolatile != p2.provolatile OR
      p1.pronargs != p2.pronargs);
- oid | proname | oid | proname 
------+---------+-----+---------
-(0 rows)
+ oid  |       proname       | oid  | proname  
+------+---------------------+------+----------
+ 8535 | rational_in_integer | 8543 | rational
+(1 row)
 
 -- Look for uses of different type OIDs in the argument/result type fields
 -- for different aliases of the same built-in function.
@@ -1301,9 +1302,20 @@ SELECT * FROM funcdescs
   WHERE prodesc IS DISTINCT FROM expecteddesc
     AND oprdesc NOT LIKE 'deprecated%'
     AND prodesc IS DISTINCT FROM oprdesc;
- p_oid | proname | o_oid | prodesc | expecteddesc | oprdesc 
--------+---------+-------+---------+--------------+---------
-(0 rows)
+ p_oid |   proname    | o_oid |  prodesc   |         expecteddesc          |        oprdesc        
+-------+--------------+-------+------------+-------------------------------+-----------------------
+  8554 | rational_eq  |  8565 | Comparison | implementation of = operator  | equal
+  8555 | rational_ne  |  8566 | Comparison | implementation of <> operator | not equal
+  8556 | rational_lt  |  8567 | Comparison | implementation of < operator  | less than
+  8557 | rational_le  |  8568 | Comparison | implementation of <= operator | less than or equal
+  8558 | rational_gt  |  8569 | Comparison | implementation of > operator  | greater than
+  8559 | rational_ge  |  8570 | Comparison | implementation of >= operator | greater than or equal
+  8547 | rational_add |  8571 | Arithmetic | implementation of + operator  | add
+  8551 | rational_neg |  8572 | Arithmetic | implementation of - operator  | negate
+  8548 | rational_sub |  8573 | Arithmetic | implementation of - operator  | subtract
+  8550 | rational_div |  8574 | Arithmetic | implementation of / operator  | divide
+  8549 | rational_mul |  8575 | Arithmetic | implementation of * operator  | multiply
+(11 rows)
 
 -- Show all the operator-implementation functions that have their own
 -- comments.  This should happen only in cases where the function and
@@ -1336,7 +1348,18 @@ ORDER BY 1;
   3940 | jsonb_extract_path_text | get value from jsonb as text with path elements
   3951 | json_extract_path       | get value from json with path elements
   3953 | json_extract_path_text  | get value from json as text with path elements
-(9 rows)
+  8547 | rational_add            | Arithmetic
+  8548 | rational_sub            | Arithmetic
+  8549 | rational_mul            | Arithmetic
+  8550 | rational_div            | Arithmetic
+  8551 | rational_neg            | Arithmetic
+  8554 | rational_eq             | Comparison
+  8555 | rational_ne             | Comparison
+  8556 | rational_lt             | Comparison
+  8557 | rational_le             | Comparison
+  8558 | rational_gt             | Comparison
+  8559 | rational_ge             | Comparison
+(20 rows)
 
 -- Operators that are commutator pairs should have identical volatility
 -- and leakproofness markings on their implementation functions.
diff --git a/src/test/regress/expected/rational.out b/src/test/regress/expected/rational.out
new file mode 100644
index 0000000000..7aa9a3e49b
--- /dev/null
+++ b/src/test/regress/expected/rational.out
@@ -0,0 +1,690 @@
+--
+-- Rational numbers
+-- I/O
+-- can parse a simple fraction
+select '1/3'::rational;
+ rational 
+----------
+ 1/3
+(1 row)
+
+-- can parse negatives
+select '-1/3'::rational;
+ rational 
+----------
+ -1/3
+(1 row)
+
+select '1/-3'::rational;
+ rational 
+----------
+ -1/3
+(1 row)
+
+-- SEND works
+select rational_send('1/3');
+   rational_send    
+--------------------
+ \x0000000100000003
+(1 row)
+
+-- constructor proc
+select rational(1,2) = '1/2'::rational;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- casting
+-- int
+select 42 = '42/1'::rational;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- from float
+select 0.263157894737::float::rational;
+ rational 
+----------
+ 5/19
+(1 row)
+
+select 3.141592625359::float::rational;
+    rational     
+-----------------
+ 4712235/1499951
+(1 row)
+
+select 0.606557377049::float::rational;
+ rational 
+----------
+ 37/61
+(1 row)
+
+select -0.5::float::rational;
+ ?column? 
+----------
+ -1/2
+(1 row)
+
+select 1.000001::float::rational;
+    rational     
+-----------------
+ 1000001/1000000
+(1 row)
+
+select 1.0000001::float::rational;
+     rational     
+------------------
+ 10000000/9999999
+(1 row)
+
+select 1.00000001::float::rational;
+      rational       
+---------------------
+ 100000001/100000000
+(1 row)
+
+select 1.000000001::float::rational;
+      rational       
+---------------------
+ 999999918/999999917
+(1 row)
+
+select 1.0000000001::float::rational;
+ rational 
+----------
+ 1/1
+(1 row)
+
+select 2147483647::float::rational;
+   rational   
+--------------
+ 2147483647/1
+(1 row)
+
+select 2147483647.1::float::rational;
+ERROR:  value too large for rational
+select 'NAN'::float::rational;
+ERROR:  value too large for rational
+-- to float
+select '1/2'::rational::float;
+ float8 
+--------
+    0.5
+(1 row)
+
+set extra_float_digits = 0; -- default changed in PG12
+select '1/3'::rational::float;
+      float8       
+-------------------
+ 0.333333333333333
+(1 row)
+
+reset extra_float_digits;
+select '-1/2'::rational::float;
+ float8 
+--------
+   -0.5
+(1 row)
+
+-- too big
+select '2147483648/2147483647'::rational;
+ERROR:  numerator or denominator outside valid int32 value
+LINE 1: select '2147483648/2147483647'::rational;
+               ^
+-- no spaces
+select '1 /3'::rational;
+ERROR:  Expecting '/' after number but found ' '
+LINE 1: select '1 /3'::rational;
+               ^
+-- no zero denominator
+select '1/0'::rational;
+ERROR:  fraction cannot have zero denominator
+LINE 1: select '1/0'::rational;
+               ^
+-- quoted number treated as int
+select '1'::rational;
+ rational 
+----------
+ 1/1
+(1 row)
+
+select '-1'::rational;
+ rational 
+----------
+ -1/1
+(1 row)
+
+-- no garbage
+select ''::rational;
+ERROR:  Missing or invalid numerator
+LINE 1: select ''::rational;
+               ^
+select '/'::rational;
+ERROR:  Missing or invalid numerator
+LINE 1: select '/'::rational;
+               ^
+select '2/'::rational;
+ERROR:  Expecting value after '/' but got '\0'
+LINE 1: select '2/'::rational;
+               ^
+select '/2'::rational;
+ERROR:  Missing or invalid numerator
+LINE 1: select '/2'::rational;
+               ^
+select 'sdfkjsdfj34984538'::rational;
+ERROR:  Missing or invalid numerator
+LINE 1: select 'sdfkjsdfj34984538'::rational;
+               ^
+-- simplification
+-- double negative becomes positive
+select rational_simplify('-1/-3');
+ rational_simplify 
+-------------------
+ 1/3
+(1 row)
+
+-- works with negative value
+select rational_simplify('-3/12');
+ rational_simplify 
+-------------------
+ -1/4
+(1 row)
+
+-- dodge the INT32_MIN/-1 mistake
+select rational_simplify('-2147483648/2147483647');
+   rational_simplify    
+------------------------
+ -2147483648/2147483647
+(1 row)
+
+-- don't move negative if it would overflow
+select rational_simplify('1/-2147483648');
+ rational_simplify 
+-------------------
+ 1/-2147483648
+(1 row)
+
+-- biggest value reduces
+select rational_simplify('2147483647/2147483647');
+ rational_simplify 
+-------------------
+ 1/1
+(1 row)
+
+-- smallest value reduces
+select rational_simplify('-2147483648/-2147483648');
+ rational_simplify 
+-------------------
+ 1/1
+(1 row)
+
+-- idempotent on simplified expression
+select rational_simplify('1/1');
+ rational_simplify 
+-------------------
+ 1/1
+(1 row)
+
+-- addition
+-- additive identity
+select '0/1'::rational + '1/2';
+ ?column? 
+----------
+ 1/2
+(1 row)
+
+-- additive inverse
+select '1/2'::rational + '-1/2';
+ ?column? 
+----------
+ 0/4
+(1 row)
+
+-- just regular
+select '1/2'::rational + '1/2';
+ ?column? 
+----------
+ 4/4
+(1 row)
+
+-- forcing intermediate simplification
+select '2147483647/2147483647'::rational + '1/1';
+ ?column? 
+----------
+ 2/1
+(1 row)
+
+-- overflow (sqrt(max)+1)/1 + 1/sqrt(max)
+select '46342/1'::rational + '1/46341';
+ERROR:  intermediate value overflow in rational addition
+-- multiplication
+-- multiplicative identity
+select '1/1'::rational * '1/2';
+ ?column? 
+----------
+ 1/2
+(1 row)
+
+-- multiplicative inverse
+select '2/1'::rational * '1/2';
+ ?column? 
+----------
+ 2/2
+(1 row)
+
+-- just regular
+select '5/8'::rational * '3/5';
+ ?column? 
+----------
+ 15/40
+(1 row)
+
+-- forcing intermediate simplification
+select '2147483647/2147483647'::rational * '2/2';
+ ?column? 
+----------
+ 2/2
+(1 row)
+
+-- overflow
+select '46342/46341'::rational * '46341/46342';
+ERROR:  intermediate value overflow in rational multiplication
+-- division
+select 1::rational / 3;
+ ?column? 
+----------
+ 1/3
+(1 row)
+
+select '2/3'::rational / '2/3';
+ ?column? 
+----------
+ 6/6
+(1 row)
+
+-- negation
+-- flips sign of numerator
+select -('1/2'::rational);
+ ?column? 
+----------
+ -1/2
+(1 row)
+
+-- flips back
+select -('-1/2'::rational);
+ ?column? 
+----------
+ 1/2
+(1 row)
+
+-- overflow not possible
+select -('-2147483648/1'::rational);
+    ?column?    
+----------------
+ -2147483648/-1
+(1 row)
+
+select -('1/-2147483648'::rational);
+    ?column?    
+----------------
+ -1/-2147483648
+(1 row)
+
+select -('-2147483648/-2147483648'::rational);
+ ?column? 
+----------
+ -1/1
+(1 row)
+
+-- subtraction
+-- just regular
+select '1/2'::rational - '1/2';
+ ?column? 
+----------
+ 0/4
+(1 row)
+
+-- can go negative
+select '1/2'::rational - '1/1';
+ ?column? 
+----------
+ -1/2
+(1 row)
+
+-- forcing intermediate simplification
+select '2147483647/2147483647'::rational - '100/100';
+ ?column? 
+----------
+ 0/100
+(1 row)
+
+-- overflow (sqrt(max)+1)/1 - 1/sqrt(max)
+select '46342/1'::rational - '1/46341';
+ERROR:  intermediate value overflow in rational addition
+-- comparison
+-- equal in every way
+select '1/1'::rational = '1/1';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- same equivalence class
+select '20/40'::rational = '22/44';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- negatives work too
+select '-20/40'::rational = '-22/44';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overflow not possible
+select '46342/46341'::rational = '46342/46341';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- high precision
+select '1/2147483647'::rational = '1/2147483646';
+ ?column? 
+----------
+ f
+(1 row)
+
+select '1/3'::rational * 3 = 1;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 1.0/3.0 = 1.0;
+ ?column? 
+----------
+ f
+(1 row)
+
+-- not everything is equal
+select '2/3'::rational = '8/5';
+ ?column? 
+----------
+ f
+(1 row)
+
+-- negates equality
+select '1/1'::rational <> '1/1';
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overflow not possible
+select '46342/46341'::rational <> '46342/46341';
+ ?column? 
+----------
+ f
+(1 row)
+
+-- not equal
+select '2/3'::rational <> '8/5';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- lt anti-reflexive
+select '1/2'::rational < '1/2';
+ ?column? 
+----------
+ f
+(1 row)
+
+-- gt anti-reflexive
+select '1/2'::rational > '1/2';
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overflow not possible
+select '1/2147483647'::rational < '2/2147483647';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- lte
+select r
+  from unnest(ARRAY[
+      '303700050/303700050',
+      '-2/1',
+      '0/9999999',
+      '-11/17',
+      '100/1',
+      '3/4',
+      '-1/2',
+      '-1/1',
+      '5/8',
+      '6/9',
+      '5/8'
+    ]::rational[]) as r
+order by r asc;
+          r          
+---------------------
+ -2/1
+ -1/1
+ -11/17
+ -1/2
+ 0/9999999
+ 5/8
+ 5/8
+ 6/9
+ 3/4
+ 303700050/303700050
+ 100/1
+(11 rows)
+
+-- gte
+select r
+  from unnest(ARRAY[
+      '303700050/303700050',
+      '-2/1',
+      '0/9999999',
+      '-11/17',
+      '100/1',
+      '3/4',
+      '-1/2',
+      '-1/1',
+      '5/8',
+      '6/9',
+      '5/8'
+    ]::rational[]) as r
+order by r desc;
+          r          
+---------------------
+ 100/1
+ 303700050/303700050
+ 3/4
+ 6/9
+ 5/8
+ 5/8
+ 0/9999999
+ -1/2
+ -11/17
+ -1/1
+ -2/1
+(11 rows)
+
+-- btree
+create table rs (
+  r rational
+);
+create index rs_r_btree on rs using btree(r);
+insert into rs values ('0/7'), ('1/7'), ('2/7'), ('3/7'),
+                      ('4/7'), ('5/7'), ('6/7');
+set enable_seqscan=false;
+explain select * from rs where r > '1/7' and r <= '10/14';
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Bitmap Heap Scan on rs  (cost=4.27..14.97 rows=11 width=8)
+   Recheck Cond: ((r > '1/7'::rational) AND (r <= '10/14'::rational))
+   ->  Bitmap Index Scan on rs_r_btree  (cost=0.00..4.27 rows=11 width=0)
+         Index Cond: ((r > '1/7'::rational) AND (r <= '10/14'::rational))
+(4 rows)
+
+select * from rs where r > '1/7' and r <= '10/14';
+  r  
+-----
+ 2/7
+ 3/7
+ 4/7
+ 5/7
+(4 rows)
+
+set enable_seqscan=true;
+drop table rs cascade;
+-- hash
+create table rs (
+  r rational
+);
+create index rs_r_hash on rs using hash(r);
+insert into rs values ('0/7'), ('1/7');
+set enable_seqscan=false;
+explain select * from rs where r = '0/1';
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Bitmap Heap Scan on rs  (cost=4.09..14.76 rows=11 width=8)
+   Recheck Cond: (r = '0/1'::rational)
+   ->  Bitmap Index Scan on rs_r_hash  (cost=0.00..4.08 rows=11 width=0)
+         Index Cond: (r = '0/1'::rational)
+(4 rows)
+
+select * from rs where r = '0/1';
+  r  
+-----
+ 0/7
+(1 row)
+
+select * from rs where r = '2/7';
+ r 
+---
+(0 rows)
+
+set enable_seqscan=true;
+drop table rs cascade;
+-- aggregates
+select min(r)
+  from unnest(ARRAY[
+      '100/1',
+      NULL,
+      '-11/17',
+      '-1/1'
+    ]::rational[]) as r;
+ min  
+------
+ -1/1
+(1 row)
+
+select max(r)
+  from unnest(ARRAY[
+      '100/1',
+      NULL,
+      '-11/17',
+      '-1/1'
+    ]::rational[]) as r;
+  max  
+-------
+ 100/1
+(1 row)
+
+select max(r)
+  from unnest(ARRAY[
+      NULL, NULL, NULL
+    ]::rational[]) as r;
+ max 
+-----
+ 
+(1 row)
+
+select rational_simplify(sum(r))
+  from unnest(ARRAY[
+      '1/1',  '1/2', NULL,
+      '-3/2', '1/16'
+    ]::rational[]) as r;
+ rational_simplify 
+-------------------
+ 1/16
+(1 row)
+
+select sum(r)
+  from unnest(ARRAY[
+      NULL, NULL, NULL
+    ]::rational[]) as r;
+ sum 
+-----
+ 
+(1 row)
+
+-- stern-brocot intermediates
+-- intermediates start at 1 -- between 0 and Infinity
+select rational_intermediate(NULL, NULL);
+ rational_intermediate 
+-----------------------
+ 1/1
+(1 row)
+
+-- random example
+select rational_intermediate('15/16', 1);
+ rational_intermediate 
+-----------------------
+ 16/17
+(1 row)
+
+select rational_intermediate('15/16', 1)
+  between '15/16'::rational and 1;
+ ?column? 
+----------
+ t
+(1 row)
+
+select rational_intermediate('44320/39365', '77200/12184');
+ rational_intermediate 
+-----------------------
+ 2/1
+(1 row)
+
+select rational_intermediate('44320/39365', '77200/12184')
+  between '44320/39365'::rational and '77200/12184';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- unbounded upper limit produces least greater integer
+select rational_intermediate('1/3', NULL);
+ rational_intermediate 
+-----------------------
+ 1/1
+(1 row)
+
+select rational_intermediate('3/2', NULL);
+ rational_intermediate 
+-----------------------
+ 2/1
+(1 row)
+
+-- though not the other direction
+select rational_intermediate(NULL, '15/16');
+ rational_intermediate 
+-----------------------
+ 1/2
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd3ea..50b4eb1220 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 pg_lsn regproc
+test: boolean char name varchar text int2 int4 int8 oid float4 float8 rational bit numeric txid uuid enum money rangetypes pg_lsn
 
 # ----------
 # The second group of parallel tests
@@ -27,7 +27,7 @@ test: strings numerology point lseg line box path polygon circle date time timet
 # geometry depends on point, lseg, box, path, polygon and circle
 # horology depends on interval, timetz, timestamp, timestamptz
 # ----------
-test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions
+test: geometry horology regex regproc oidjoins type_sanity opr_sanity misc_sanity comments expressions
 
 # ----------
 # These four each depend on the previous one
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index acba391332..9a32246d58 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -12,6 +12,7 @@ test: int8
 test: oid
 test: float4
 test: float8
+test: rational
 test: bit
 test: numeric
 test: txid
diff --git a/src/test/regress/sql/rational.sql b/src/test/regress/sql/rational.sql
new file mode 100644
index 0000000000..48462ad8d3
--- /dev/null
+++ b/src/test/regress/sql/rational.sql
@@ -0,0 +1,272 @@
+--
+-- Rational numbers
+
+-- I/O
+
+-- can parse a simple fraction
+select '1/3'::rational;
+-- can parse negatives
+select '-1/3'::rational;
+select '1/-3'::rational;
+-- SEND works
+select rational_send('1/3');
+
+-- constructor proc
+select rational(1,2) = '1/2'::rational;
+
+-- casting
+
+-- int
+select 42 = '42/1'::rational;
+
+-- from float
+select 0.263157894737::float::rational;
+select 3.141592625359::float::rational;
+select 0.606557377049::float::rational;
+select -0.5::float::rational;
+select 1.000001::float::rational;
+select 1.0000001::float::rational;
+select 1.00000001::float::rational;
+select 1.000000001::float::rational;
+select 1.0000000001::float::rational;
+select 2147483647::float::rational;
+select 2147483647.1::float::rational;
+select 'NAN'::float::rational;
+
+-- to float
+select '1/2'::rational::float;
+set extra_float_digits = 0; -- default changed in PG12
+select '1/3'::rational::float;
+reset extra_float_digits;
+select '-1/2'::rational::float;
+
+-- too big
+select '2147483648/2147483647'::rational;
+-- no spaces
+select '1 /3'::rational;
+-- no zero denominator
+select '1/0'::rational;
+-- quoted number treated as int
+select '1'::rational;
+select '-1'::rational;
+-- no garbage
+select ''::rational;
+select '/'::rational;
+select '2/'::rational;
+select '/2'::rational;
+select 'sdfkjsdfj34984538'::rational;
+
+-- simplification
+
+-- double negative becomes positive
+select rational_simplify('-1/-3');
+-- works with negative value
+select rational_simplify('-3/12');
+-- dodge the INT32_MIN/-1 mistake
+select rational_simplify('-2147483648/2147483647');
+-- don't move negative if it would overflow
+select rational_simplify('1/-2147483648');
+-- biggest value reduces
+select rational_simplify('2147483647/2147483647');
+-- smallest value reduces
+select rational_simplify('-2147483648/-2147483648');
+-- idempotent on simplified expression
+select rational_simplify('1/1');
+
+-- addition
+
+-- additive identity
+select '0/1'::rational + '1/2';
+-- additive inverse
+select '1/2'::rational + '-1/2';
+-- just regular
+select '1/2'::rational + '1/2';
+-- forcing intermediate simplification
+select '2147483647/2147483647'::rational + '1/1';
+-- overflow (sqrt(max)+1)/1 + 1/sqrt(max)
+select '46342/1'::rational + '1/46341';
+
+-- multiplication
+
+-- multiplicative identity
+select '1/1'::rational * '1/2';
+-- multiplicative inverse
+select '2/1'::rational * '1/2';
+-- just regular
+select '5/8'::rational * '3/5';
+-- forcing intermediate simplification
+select '2147483647/2147483647'::rational * '2/2';
+-- overflow
+select '46342/46341'::rational * '46341/46342';
+
+-- division
+
+select 1::rational / 3;
+select '2/3'::rational / '2/3';
+
+-- negation
+
+-- flips sign of numerator
+select -('1/2'::rational);
+-- flips back
+select -('-1/2'::rational);
+-- overflow not possible
+select -('-2147483648/1'::rational);
+select -('1/-2147483648'::rational);
+select -('-2147483648/-2147483648'::rational);
+
+-- subtraction
+
+-- just regular
+select '1/2'::rational - '1/2';
+-- can go negative
+select '1/2'::rational - '1/1';
+-- forcing intermediate simplification
+select '2147483647/2147483647'::rational - '100/100';
+-- overflow (sqrt(max)+1)/1 - 1/sqrt(max)
+select '46342/1'::rational - '1/46341';
+
+-- comparison
+
+-- equal in every way
+select '1/1'::rational = '1/1';
+-- same equivalence class
+select '20/40'::rational = '22/44';
+-- negatives work too
+select '-20/40'::rational = '-22/44';
+-- overflow not possible
+select '46342/46341'::rational = '46342/46341';
+-- high precision
+select '1/2147483647'::rational = '1/2147483646';
+select '1/3'::rational * 3 = 1;
+select 1.0/3.0 = 1.0;
+-- not everything is equal
+select '2/3'::rational = '8/5';
+
+-- negates equality
+select '1/1'::rational <> '1/1';
+-- overflow not possible
+select '46342/46341'::rational <> '46342/46341';
+-- not equal
+select '2/3'::rational <> '8/5';
+
+-- lt anti-reflexive
+select '1/2'::rational < '1/2';
+-- gt anti-reflexive
+select '1/2'::rational > '1/2';
+-- overflow not possible
+select '1/2147483647'::rational < '2/2147483647';
+
+-- lte
+select r
+  from unnest(ARRAY[
+      '303700050/303700050',
+      '-2/1',
+      '0/9999999',
+      '-11/17',
+      '100/1',
+      '3/4',
+      '-1/2',
+      '-1/1',
+      '5/8',
+      '6/9',
+      '5/8'
+    ]::rational[]) as r
+order by r asc;
+-- gte
+select r
+  from unnest(ARRAY[
+      '303700050/303700050',
+      '-2/1',
+      '0/9999999',
+      '-11/17',
+      '100/1',
+      '3/4',
+      '-1/2',
+      '-1/1',
+      '5/8',
+      '6/9',
+      '5/8'
+    ]::rational[]) as r
+order by r desc;
+
+-- btree
+create table rs (
+  r rational
+);
+create index rs_r_btree on rs using btree(r);
+insert into rs values ('0/7'), ('1/7'), ('2/7'), ('3/7'),
+                      ('4/7'), ('5/7'), ('6/7');
+set enable_seqscan=false;
+
+explain select * from rs where r > '1/7' and r <= '10/14';
+select * from rs where r > '1/7' and r <= '10/14';
+
+set enable_seqscan=true;
+drop table rs cascade;
+
+-- hash
+create table rs (
+  r rational
+);
+create index rs_r_hash on rs using hash(r);
+insert into rs values ('0/7'), ('1/7');
+set enable_seqscan=false;
+
+explain select * from rs where r = '0/1';
+select * from rs where r = '0/1';
+select * from rs where r = '2/7';
+
+set enable_seqscan=true;
+drop table rs cascade;
+
+-- aggregates
+
+select min(r)
+  from unnest(ARRAY[
+      '100/1',
+      NULL,
+      '-11/17',
+      '-1/1'
+    ]::rational[]) as r;
+
+select max(r)
+  from unnest(ARRAY[
+      '100/1',
+      NULL,
+      '-11/17',
+      '-1/1'
+    ]::rational[]) as r;
+
+select max(r)
+  from unnest(ARRAY[
+      NULL, NULL, NULL
+    ]::rational[]) as r;
+
+select rational_simplify(sum(r))
+  from unnest(ARRAY[
+      '1/1',  '1/2', NULL,
+      '-3/2', '1/16'
+    ]::rational[]) as r;
+
+select sum(r)
+  from unnest(ARRAY[
+      NULL, NULL, NULL
+    ]::rational[]) as r;
+
+-- stern-brocot intermediates
+
+-- intermediates start at 1 -- between 0 and Infinity
+select rational_intermediate(NULL, NULL);
+-- random example
+select rational_intermediate('15/16', 1);
+select rational_intermediate('15/16', 1)
+  between '15/16'::rational and 1;
+select rational_intermediate('44320/39365', '77200/12184');
+select rational_intermediate('44320/39365', '77200/12184')
+  between '44320/39365'::rational and '77200/12184';
+-- unbounded upper limit produces least greater integer
+select rational_intermediate('1/3', NULL);
+select rational_intermediate('3/2', NULL);
+-- though not the other direction
+select rational_intermediate(NULL, '15/16');
