From e2dd9427d51c50898d9558c201f6d5df61e957a7 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:25:39 -0700
Subject: [PATCH v7 01/13] multirange type basics

---
 src/backend/catalog/pg_proc.c                 |  16 +-
 src/backend/catalog/pg_range.c                |  11 +-
 src/backend/catalog/pg_type.c                 | 124 ++-
 src/backend/commands/typecmds.c               | 171 +++-
 src/backend/parser/parse_coerce.c             | 303 +++++-
 src/backend/utils/adt/Makefile                |   1 +
 src/backend/utils/adt/multirangetypes.c       | 966 ++++++++++++++++++
 src/backend/utils/adt/pseudotypes.c           |  25 +
 src/backend/utils/adt/rangetypes.c            | 155 ++-
 src/backend/utils/cache/lsyscache.c           |  62 ++
 src/backend/utils/cache/syscache.c            |  11 +
 src/backend/utils/cache/typcache.c            | 109 +-
 src/backend/utils/fmgr/funcapi.c              | 271 ++++-
 src/include/access/tupmacs.h                  |   4 +-
 src/include/catalog/indexing.h                |   3 +
 src/include/catalog/pg_amop.dat               |  22 +
 src/include/catalog/pg_amproc.dat             |   7 +
 src/include/catalog/pg_opclass.dat            |   4 +
 src/include/catalog/pg_operator.dat           |  33 +
 src/include/catalog/pg_opfamily.dat           |   4 +
 src/include/catalog/pg_proc.dat               |  77 ++
 src/include/catalog/pg_range.dat              |  15 +-
 src/include/catalog/pg_range.h                |   5 +-
 src/include/catalog/pg_type.dat               |  39 +
 src/include/catalog/pg_type.h                 |   8 +-
 src/include/utils/lsyscache.h                 |   3 +
 src/include/utils/multirangetypes.h           |  72 ++
 src/include/utils/rangetypes.h                |   2 +
 src/include/utils/syscache.h                  |   1 +
 src/include/utils/typcache.h                  |   6 +
 src/pl/plpgsql/src/pl_comp.c                  |   5 +
 src/test/regress/expected/hash_func.out       |  13 +
 src/test/regress/expected/multirangetypes.out | 292 ++++++
 src/test/regress/expected/opr_sanity.out      |  23 +-
 src/test/regress/expected/type_sanity.out     |  46 +-
 src/test/regress/parallel_schedule            |   3 +-
 src/test/regress/serial_schedule              |   1 +
 src/test/regress/sql/hash_func.sql            |  10 +
 src/test/regress/sql/multirangetypes.sql      |  65 ++
 src/test/regress/sql/opr_sanity.sql           |  18 +-
 src/test/regress/sql/type_sanity.sql          |  16 +-
 41 files changed, 2890 insertions(+), 132 deletions(-)
 create mode 100644 src/backend/utils/adt/multirangetypes.c
 create mode 100644 src/include/utils/multirangetypes.h
 create mode 100644 src/test/regress/expected/multirangetypes.out
 create mode 100644 src/test/regress/sql/multirangetypes.sql

diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index ef009ad2bc..80cc0721a5 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -192,6 +192,7 @@ ProcedureCreate(const char *procedureName,
 				genericInParam = true;
 				break;
 			case ANYRANGEOID:
+			case ANYMULTIRANGEOID:
 				genericInParam = true;
 				anyrangeInParam = true;
 				break;
@@ -219,6 +220,7 @@ ProcedureCreate(const char *procedureName,
 					genericOutParam = true;
 					break;
 				case ANYRANGEOID:
+				case ANYMULTIRANGEOID:
 					genericOutParam = true;
 					anyrangeOutParam = true;
 					break;
@@ -231,10 +233,10 @@ ProcedureCreate(const char *procedureName,
 
 	/*
 	 * Do not allow polymorphic return type unless at least one input argument
-	 * is polymorphic.  ANYRANGE return type is even stricter: must have an
-	 * ANYRANGE input (since we can't deduce the specific range type from
-	 * ANYELEMENT).  Also, do not allow return type INTERNAL unless at least
-	 * one input argument is INTERNAL.
+	 * is polymorphic.  ANYRANGE and ANYMULTIRANGE return types are even
+	 * stricter: must have an ANYRANGE or ANYMULTIRANGE input (since we can't
+	 * deduce the specific range type from ANYELEMENT).  Also, do not allow
+	 * return type INTERNAL unless at least one input argument is INTERNAL.
 	 */
 	if ((IsPolymorphicType(returnType) || genericOutParam)
 		&& !genericInParam)
@@ -243,12 +245,12 @@ ProcedureCreate(const char *procedureName,
 				 errmsg("cannot determine result data type"),
 				 errdetail("A function returning a polymorphic type must have at least one polymorphic argument.")));
 
-	if ((returnType == ANYRANGEOID || anyrangeOutParam) &&
-		!anyrangeInParam)
+	if ((returnType == ANYRANGEOID || returnType == ANYMULTIRANGEOID
+		 || anyrangeOutParam) && !anyrangeInParam)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
 				 errmsg("cannot determine result data type"),
-				 errdetail("A function returning \"anyrange\" must have at least one \"anyrange\" argument.")));
+				 errdetail("A function returning \"anyrange\" or \"anymultirange\" must have at least one \"anyrange\" or \"anymultirange\" argument.")));
 
 	if ((returnType == INTERNALOID || internalOutParam) && !internalInParam)
 		ereport(ERROR,
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index e6e138babd..d914f04858 100644
--- a/src/backend/catalog/pg_range.c
+++ b/src/backend/catalog/pg_range.c
@@ -35,7 +35,7 @@
 void
 RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 			Oid rangeSubOpclass, RegProcedure rangeCanonical,
-			RegProcedure rangeSubDiff)
+			RegProcedure rangeSubDiff, Oid multirangeTypeOid)
 {
 	Relation	pg_range;
 	Datum		values[Natts_pg_range];
@@ -43,6 +43,7 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 	HeapTuple	tup;
 	ObjectAddress myself;
 	ObjectAddress referenced;
+	ObjectAddress referencing;
 
 	pg_range = table_open(RangeRelationId, RowExclusiveLock);
 
@@ -54,6 +55,7 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 	values[Anum_pg_range_rngsubopc - 1] = ObjectIdGetDatum(rangeSubOpclass);
 	values[Anum_pg_range_rngcanonical - 1] = ObjectIdGetDatum(rangeCanonical);
 	values[Anum_pg_range_rngsubdiff - 1] = ObjectIdGetDatum(rangeSubDiff);
+	values[Anum_pg_range_mltrngtypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,13 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
+	/* record multirange type's dependency on the range type */
+
+	referencing.classId = TypeRelationId;
+	referencing.objectId = multirangeTypeOid;
+	referencing.objectSubId = 0;
+	recordDependencyOn(&referencing, &myself, DEPENDENCY_INTERNAL);
+
 	table_close(pg_range, RowExclusiveLock);
 }
 
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index d65a2d971f..8a85d3412a 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -36,6 +36,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static int	makeUniqueTypeName(char *dest, const char *typeName, size_t namelen,
+							   Oid typeNamespace, bool tryOriginalName);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -785,29 +788,10 @@ makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
 	char	   *arr = (char *) palloc(NAMEDATALEN);
 	int			namelen = strlen(typeName);
-	int			i;
+	int			underscores;
 
-	/*
-	 * The idea is to prepend underscores as needed until we make a name that
-	 * doesn't collide with anything...
-	 */
-	for (i = 1; i < NAMEDATALEN - 1; i++)
-	{
-		arr[i - 1] = '_';
-		if (i + namelen < NAMEDATALEN)
-			strcpy(arr + i, typeName);
-		else
-		{
-			memcpy(arr + i, typeName, NAMEDATALEN - i);
-			truncate_identifier(arr, NAMEDATALEN, false);
-		}
-		if (!SearchSysCacheExists2(TYPENAMENSP,
-								   CStringGetDatum(arr),
-								   ObjectIdGetDatum(typeNamespace)))
-			break;
-	}
-
-	if (i >= NAMEDATALEN - 1)
+	underscores = makeUniqueTypeName(arr, typeName, namelen, typeNamespace, false);
+	if (underscores >= NAMEDATALEN - 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -878,3 +862,99 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * the caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char		mltrng[NAMEDATALEN];
+	char	   *mltrngunique = (char *) palloc(NAMEDATALEN);
+	int			namelen = strlen(rangeTypeName);
+	int			rangelen;
+	char	   *rangestr;
+	int			rangeoffset;
+	int			underscores;
+
+	/*
+	 * If the range type name contains "range" then change that to
+	 * "multirange". Otherwise add "multirange" to the end. After that,
+	 * prepend underscores as needed until we make a name that doesn't collide
+	 * with anything...
+	 */
+	strlcpy(mltrng, rangeTypeName, NAMEDATALEN);
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		rangeoffset = rangestr - rangeTypeName;
+		rangelen = strlen(rangestr);
+		strlcpy(mltrng + rangeoffset, "multi", NAMEDATALEN - rangeoffset);
+		strlcpy(mltrng + rangeoffset + 5, rangestr, NAMEDATALEN - rangeoffset - 5);
+		namelen += 5;
+	}
+	else
+	{
+		strlcpy(mltrng + namelen, "multirange", NAMEDATALEN - namelen);
+		namelen += 10;
+	}
+
+	underscores = makeUniqueTypeName(mltrngunique, mltrng, namelen, typeNamespace, true);
+	if (underscores >= NAMEDATALEN - 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mltrngunique;
+}
+
+/*
+ * makeUniqueTypeName: Prepend underscores as needed until we make a name that
+ * doesn't collide with anything. Tries the original typeName if requested.
+ *
+ * Returns the number of underscores added.
+ */
+int
+makeUniqueTypeName(char *dest, const char *typeName, size_t namelen, Oid typeNamespace,
+				   bool tryOriginalName)
+{
+	int			i;
+
+	for (i = 0; i < NAMEDATALEN - 1; i++)
+	{
+		if (i == 0)
+		{
+			if (tryOriginalName &&
+				!SearchSysCacheExists2(TYPENAMENSP,
+									   CStringGetDatum(typeName),
+									   ObjectIdGetDatum(typeNamespace)))
+			{
+				strcpy(dest, typeName);
+				break;
+			}
+
+		}
+		else
+		{
+			dest[i - 1] = '_';
+			if (i + namelen < NAMEDATALEN)
+				strcpy(dest + i, typeName);
+			else
+			{
+				strlcpy(dest + i, typeName, NAMEDATALEN);
+				truncate_identifier(dest, NAMEDATALEN, false);
+			}
+			if (!SearchSysCacheExists2(TYPENAMENSP,
+									   CStringGetDatum(dest),
+									   ObjectIdGetDatum(typeNamespace)))
+				break;
+		}
+	}
+
+	return i;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 89887b8fd7..14a6857062 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -88,6 +88,8 @@ Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
 
 static void makeRangeConstructors(const char *name, Oid namespace,
 								  Oid rangeOid, Oid subtype);
+static void makeMultirangeConstructors(const char *name, Oid namespace,
+									   Oid multirangeOid, Oid rangeArrayOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -811,7 +813,8 @@ DefineDomain(CreateDomainStmt *stmt)
 		typtype != TYPTYPE_COMPOSITE &&
 		typtype != TYPTYPE_DOMAIN &&
 		typtype != TYPTYPE_ENUM &&
-		typtype != TYPTYPE_RANGE)
+		typtype != TYPTYPE_RANGE &&
+		typtype != TYPTYPE_MULTIRANGE)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("\"%s\" is not a valid base type for a domain",
@@ -1353,8 +1356,12 @@ DefineRange(CreateRangeStmt *stmt)
 	char	   *typeName;
 	Oid			typeNamespace;
 	Oid			typoid;
+	Oid			mltrngtypoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1371,6 +1378,7 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1522,6 +1530,9 @@ DefineRange(CreateRangeStmt *stmt)
 	/* Allocate OID for array type */
 	rangeArrayOid = AssignTypeArrayOid();
 
+	/* Allocate OID for multirange array type */
+	multirangeArrayOid = AssignTypeArrayOid();
+
 	/* Create the pg_type entry */
 	address =
 		TypeCreate(InvalidOid,	/* no predetermined type OID */
@@ -1557,9 +1568,47 @@ DefineRange(CreateRangeStmt *stmt)
 				   InvalidOid); /* type's collation (ranges never have one) */
 	Assert(typoid == address.objectId);
 
+	/* Create the multirange that goes with it */
+
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(InvalidOid,	/* no predetermined type OID */
+				   multirangeTypeName,	/* type name */
+				   typeNamespace,	/* namespace */
+				   InvalidOid,	/* relation oid (n/a here) */
+				   0,			/* relation kind (ditto) */
+				   GetUserId(), /* owner's ID */
+				   -1,			/* internal size (always varlena) */
+				   TYPTYPE_MULTIRANGE,	/* type-type (multirange type) */
+				   TYPCATEGORY_MULTIRANGE,	/* type-category (multirange type) */
+				   false,		/* multirange types are never preferred */
+				   DEFAULT_TYPDELIM,	/* array element delimiter */
+				   F_MULTIRANGE_IN, /* input procedure */
+				   F_MULTIRANGE_OUT,	/* output procedure */
+				   F_MULTIRANGE_RECV,	/* receive procedure */
+				   F_MULTIRANGE_SEND,	/* send procedure */
+				   InvalidOid,	/* typmodin procedure - none */
+				   InvalidOid,	/* typmodout procedure - none */
+				   F_MULTIRANGE_TYPANALYZE, /* analyze procedure */
+				   InvalidOid,	/* element type ID - none */
+				   false,		/* this is not an array type */
+				   multirangeArrayOid,	/* array type we are about to create */
+				   InvalidOid,	/* base type ID (only for domains) */
+				   NULL,		/* never a default type value */
+				   NULL,		/* no binary form available either */
+				   false,		/* never passed by value */
+				   alignment,	/* alignment */
+				   'x',			/* TOAST strategy (always extended) */
+				   -1,			/* typMod (Domains only) */
+				   0,			/* Array dimensions of typbasetype */
+				   false,		/* Type NOT NULL */
+				   InvalidOid); /* type's collation (ranges never have one) */
+	mltrngtypoid = mltrngaddress.objectId;
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, mltrngtypoid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1600,8 +1649,49 @@ DefineRange(CreateRangeStmt *stmt)
 
 	pfree(rangeArrayName);
 
+	/* Create the multirange's array type */
+
+	multirangeArrayName = makeArrayTypeName(multirangeTypeName, typeNamespace);
+
+	TypeCreate(multirangeArrayOid,	/* force assignment of this type OID */
+			   multirangeArrayName, /* type name */
+			   typeNamespace,	/* namespace */
+			   InvalidOid,		/* relation oid (n/a here) */
+			   0,				/* relation kind (ditto) */
+			   GetUserId(),		/* owner's ID */
+			   -1,				/* internal size (always varlena) */
+			   TYPTYPE_BASE,	/* type-type (base type) */
+			   TYPCATEGORY_ARRAY,	/* type-category (array) */
+			   false,			/* array types are never preferred */
+			   DEFAULT_TYPDELIM,	/* array element delimiter */
+			   F_ARRAY_IN,		/* input procedure */
+			   F_ARRAY_OUT,		/* output procedure */
+			   F_ARRAY_RECV,	/* receive procedure */
+			   F_ARRAY_SEND,	/* send procedure */
+			   InvalidOid,		/* typmodin procedure - none */
+			   InvalidOid,		/* typmodout procedure - none */
+			   F_ARRAY_TYPANALYZE,	/* analyze procedure */
+			   mltrngtypoid,	/* element type ID */
+			   true,			/* yes this is an array type */
+			   InvalidOid,		/* no further array type */
+			   InvalidOid,		/* base type ID */
+			   NULL,			/* never a default type value */
+			   NULL,			/* binary default isn't sent either */
+			   false,			/* never passed by value */
+			   alignment,		/* alignment - same as range's */
+			   'x',				/* ARRAY is always toastable */
+			   -1,				/* typMod (Domains only) */
+			   0,				/* Array dimensions of typbasetype */
+			   false,			/* Type NOT NULL */
+			   InvalidOid);		/* typcollation */
+
 	/* And create the constructor functions for this range type */
 	makeRangeConstructors(typeName, typeNamespace, typoid, rangeSubtype);
+	makeMultirangeConstructors(multirangeTypeName, typeNamespace,
+							   mltrngtypoid, rangeArrayOid);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1679,6 +1769,83 @@ makeRangeConstructors(const char *name, Oid namespace,
 	}
 }
 
+/*
+ * We make a separate multirange constructor for each range type
+ * so its name can include the base type, like range constructors do.
+ * If we had an anyrangearray polymorphic type we could use it here,
+ * but since each type has its own constructor name there's no need.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeArrayOid)
+{
+	static const char *const prosrc[2] = {"multirange_constructor0",
+	"multirange_constructor1"};
+	static const int pronargs[2] = {0, 1};
+
+	Oid			constructorArgTypes[0];
+	ObjectAddress myself,
+				referenced;
+	int			i;
+
+	constructorArgTypes[0] = rangeArrayOid;
+
+	Datum		allParamTypes[1] = {ObjectIdGetDatum(rangeArrayOid)};
+	ArrayType  *allParameterTypes = construct_array(allParamTypes, 1, OIDOID,
+													sizeof(Oid), true, 'i');
+	Datum		constructorAllParamTypes[2] = {PointerGetDatum(NULL), PointerGetDatum(allParameterTypes)};
+
+	Datum		paramModes[1] = {CharGetDatum(FUNC_PARAM_VARIADIC)};
+	ArrayType  *parameterModes = construct_array(paramModes, 1, CHAROID,
+												 1, true, 'c');
+	Datum		constructorParamModes[2] = {PointerGetDatum(NULL), PointerGetDatum(parameterModes)};
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	for (i = 0; i < lengthof(prosrc); i++)
+	{
+		oidvector  *constructorArgTypesVector;
+
+		constructorArgTypesVector = buildoidvector(constructorArgTypes,
+												   pronargs[i]);
+
+		myself = ProcedureCreate(name,	/* name: same as multirange type */
+								 namespace, /* namespace */
+								 false, /* replace */
+								 false, /* returns set */
+								 multirangeOid, /* return type */
+								 BOOTSTRAP_SUPERUSERID, /* proowner */
+								 INTERNALlanguageId,	/* language */
+								 F_FMGR_INTERNAL_VALIDATOR, /* language validator */
+								 prosrc[i], /* prosrc */
+								 NULL,	/* probin */
+								 PROKIND_FUNCTION,
+								 false, /* security_definer */
+								 false, /* leakproof */
+								 false, /* isStrict */
+								 PROVOLATILE_IMMUTABLE, /* volatility */
+								 PROPARALLEL_SAFE,	/* parallel safety */
+								 constructorArgTypesVector, /* parameterTypes */
+								 constructorAllParamTypes[i],	/* allParameterTypes */
+								 constructorParamModes[i],	/* parameterModes */
+								 PointerGetDatum(NULL), /* parameterNames */
+								 NIL,	/* parameterDefaults */
+								 PointerGetDatum(NULL), /* trftypes */
+								 PointerGetDatum(NULL), /* proconfig */
+								 InvalidOid,	/* prosupport */
+								 1.0,	/* procost */
+								 0.0);	/* prorows */
+
+		/*
+		 * Make the constructors internally-dependent on the multirange type
+		 * so that they go away silently when the type is dropped.  Note that
+		 * pg_dump depends on this choice to avoid dumping the constructors.
+		 */
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	}
+}
 
 /*
  * Find suitable I/O functions for a type.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index a6c51a95da..4c7a5f7fbe 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -187,18 +187,20 @@ coerce_type(ParseState *pstate, Node *node,
 	}
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
-		targetTypeId == ANYRANGEOID)
+		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID)
 	{
 		/*
 		 * Assume can_coerce_type verified that implicit coercion is okay.
 		 *
 		 * These cases are unlike the ones above because the exposed type of
-		 * the argument must be an actual array, enum, or range type.  In
-		 * particular the argument must *not* be an UNKNOWN constant.  If it
-		 * is, we just fall through; below, we'll call anyarray_in,
-		 * anyenum_in, or anyrange_in, which will produce an error.  Also, if
-		 * what we have is a domain over array, enum, or range, we have to
-		 * relabel it to its base type.
+		 * the argument must be an actual array, enum, range, or multirange
+		 * type.  In particular the argument must *not* be an UNKNOWN
+		 * constant.  If it is, we just fall through; below, we'll call
+		 * anyarray_in, anyenum_in, anyrange_in, or anymultirange_in, which
+		 * will produce an error.  Also, if what we have is a domain over
+		 * array, enum, range, or multirange, we have to relabel it to its
+		 * base type.
 		 *
 		 * Note: currently, we can't actually see a domain-over-enum here,
 		 * since the other functions in this file will not match such a
@@ -1482,6 +1484,8 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 	Oid			array_typelem;
 	Oid			range_typeid = InvalidOid;
 	Oid			range_typelem;
+	Oid			multirange_typeid = InvalidOid;
+	Oid			multirange_typelem;
 	bool		have_anyelement = false;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
@@ -1528,6 +1532,15 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 				return false;
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			if (actual_type == UNKNOWNOID)
+				continue;
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(multirange_typeid) && actual_type != multirange_typeid)
+				return false;
+			multirange_typeid = actual_type;
+		}
 	}
 
 	/* Get the element type based on the array type, if we have one */
@@ -1580,6 +1593,37 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 		}
 	}
 
+	/* Get the element type based on the multirange type, if we have one */
+	if (OidIsValid(multirange_typeid))
+	{
+		multirange_typelem = get_multirange_subtype(multirange_typeid);
+		if (!OidIsValid(multirange_typelem))
+			return false;		/* should be a multirange, but isn't */
+
+		if (!OidIsValid(range_typeid))
+		{
+			/*
+			 * If we don't have a range type yet, use the one we just got
+			 */
+			range_typeid = multirange_typelem;
+			range_typelem = get_range_subtype(multirange_typelem);
+			if (!OidIsValid(range_typelem))
+				return false;	/* should be a range, but isn't */
+		}
+		if (!OidIsValid(elem_typeid))
+		{
+			/*
+			 * If we don't have an element type yet, use the one we just got
+			 */
+			elem_typeid = range_typelem;
+		}
+		else if (range_typelem != elem_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+	}
+
 	if (have_anynonarray)
 	{
 		/* require the element type to not be an array or domain over array */
@@ -1681,13 +1725,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 	Oid			elem_typeid = InvalidOid;
 	Oid			array_typeid = InvalidOid;
 	Oid			range_typeid = InvalidOid;
+	Oid			multirange_typeid = InvalidOid;
 	Oid			array_typelem;
-	Oid			range_typelem;
+	Oid			range_typelem = InvalidOid;
+	Oid			multirange_typelem;
 	bool		have_anyelement = (rettype == ANYELEMENTOID ||
 								   rettype == ANYNONARRAYOID ||
 								   rettype == ANYENUMOID);
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
+	bool		have_anyrange = (rettype == ANYRANGEOID);
+	bool		have_anymultirange = (rettype == ANYMULTIRANGEOID);
 
 	/*
 	 * Loop through the arguments to see if we have any that are polymorphic.
@@ -1745,7 +1793,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		}
 		else if (decl_type == ANYRANGEOID)
 		{
-			have_generics = true;
+			have_generics = have_anyrange = true;
 			if (actual_type == UNKNOWNOID)
 			{
 				have_unknowns = true;
@@ -1763,6 +1811,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			have_generics = have_anymultirange = true;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_unknowns = true;
+				continue;
+			}
+			if (allow_poly && decl_type == actual_type)
+				continue;		/* no new information here */
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(multirange_typeid) && actual_type != multirange_typeid)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("arguments declared \"anymultirange\" are not all alike"),
+						 errdetail("%s versus %s",
+								   format_type_be(multirange_typeid),
+								   format_type_be(actual_type))));
+			multirange_typeid = actual_type;
+		}
 	}
 
 	/*
@@ -1813,9 +1881,12 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 	/* Get the element type based on the range type, if we have one */
 	if (OidIsValid(range_typeid))
 	{
-		if (range_typeid == ANYRANGEOID && !have_anyelement)
+		if (range_typeid == ANYRANGEOID && !have_anyelement && !have_anymultirange)
 		{
-			/* Special case for ANYRANGE input: okay iff no ANYELEMENT */
+			/*
+			 * Special case for ANYRANGE input: okay iff no ANYELEMENT or
+			 * ANYMULTIRANGE
+			 */
 			range_typelem = ANYELEMENTOID;
 		}
 		else
@@ -1849,6 +1920,69 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		}
 	}
 
+	/* Get the range type based on the multirange type, if we have one */
+	if (OidIsValid(multirange_typeid))
+	{
+		if (multirange_typeid == ANYMULTIRANGEOID && !have_anyrange && !have_anyelement)
+		{
+			/*
+			 * Special case for ANYMULTIRANGE input: okay iff no ANYRANGE or
+			 * ANYELEMENT
+			 */
+			multirange_typelem = ANYRANGEOID;
+		}
+		else
+		{
+			multirange_typelem = get_multirange_subtype(multirange_typeid);
+			if (!OidIsValid(multirange_typelem))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("argument declared %s is not a multirange type but type %s",
+								"anymultirange",
+								format_type_be(multirange_typeid))));
+		}
+
+		if (!OidIsValid(range_typeid))
+		{
+			/*
+			 * If we don't have a range type yet, use the one we just got
+			 */
+			range_typeid = multirange_typelem;
+			range_typelem = get_range_subtype(range_typeid);
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s is not consistent with argument declared %s",
+							"anymultirange", "anyrange"),
+					 errdetail("%s versus %s",
+							   format_type_be(multirange_typeid),
+							   format_type_be(range_typeid))));
+		}
+
+		/*
+		 * Infer the element type too if needed (if there is an ANYMULTIRANGE
+		 * and ANYELEMENT, with no ANYRANGE).
+		 */
+		if (!OidIsValid(elem_typeid))
+		{
+			elem_typeid = get_range_subtype(range_typeid);
+		}
+		else if (range_typelem != elem_typeid)
+		{
+			/* otherwise, they better match */
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s is not consistent with argument declared %s",
+							"anymultirange", "anyelement"),
+					 errdetail("%s versus %s",
+							   format_type_be(multirange_typeid),
+							   format_type_be(elem_typeid))));
+		}
+	}
+
 	if (!OidIsValid(elem_typeid))
 	{
 		if (allow_poly)
@@ -1856,6 +1990,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 			elem_typeid = ANYELEMENTOID;
 			array_typeid = ANYARRAYOID;
 			range_typeid = ANYRANGEOID;
+			multirange_typeid = ANYMULTIRANGEOID;
 		}
 		else
 		{
@@ -1928,6 +2063,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				}
 				declared_arg_types[j] = range_typeid;
 			}
+			else if (decl_type == ANYMULTIRANGEOID)
+			{
+				if (!OidIsValid(multirange_typeid))
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_OBJECT),
+							 errmsg("could not find multirange type for data type %s",
+									format_type_be(elem_typeid))));
+				}
+				declared_arg_types[j] = multirange_typeid;
+			}
 		}
 	}
 
@@ -1959,6 +2105,22 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		return range_typeid;
 	}
 
+	/* if we return ANYMULTIRANGE use the appropriate argument type */
+	if (rettype == ANYMULTIRANGEOID)
+	{
+		if (!OidIsValid(multirange_typeid))
+		{
+			if (OidIsValid(range_typeid))
+				multirange_typeid = get_range_multirange(range_typeid);
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("could not find multirange type for data type %s",
+								format_type_be(elem_typeid))));
+		}
+		return multirange_typeid;
+	}
+
 	/* if we return ANYELEMENT use the appropriate argument type */
 	if (rettype == ANYELEMENTOID ||
 		rettype == ANYNONARRAYOID ||
@@ -2007,8 +2169,7 @@ resolve_generic_type(Oid declared_type,
 		}
 		else if (context_declared_type == ANYELEMENTOID ||
 				 context_declared_type == ANYNONARRAYOID ||
-				 context_declared_type == ANYENUMOID ||
-				 context_declared_type == ANYRANGEOID)
+				 context_declared_type == ANYENUMOID)
 		{
 			/* Use the array type corresponding to actual type */
 			Oid			array_typeid = get_array_type(context_actual_type);
@@ -2020,11 +2181,37 @@ resolve_generic_type(Oid declared_type,
 								format_type_be(context_actual_type))));
 			return array_typeid;
 		}
+		else if (context_declared_type == ANYRANGEOID)
+		{
+			Oid			context_base_type = getBaseType(context_actual_type);
+			Oid			range_typelem = get_range_subtype(context_base_type);
+			Oid			array_typeid = get_array_type(range_typelem);
+
+			if (!OidIsValid(array_typeid))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("could not find array type for data type %s",
+								format_type_be(range_typelem))));
+			return array_typeid;
+		}
+		else if (context_declared_type == ANYMULTIRANGEOID)
+		{
+			Oid			context_base_type = getBaseType(context_actual_type);
+			Oid			multirange_typelem = get_multirange_subtype(context_base_type);
+			Oid			range_typelem = get_range_subtype(multirange_typelem);
+			Oid			array_typeid = get_array_type(range_typelem);
+
+			if (!OidIsValid(array_typeid))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("could not find array type for data type %s",
+								format_type_be(range_typelem))));
+			return array_typeid;
+		}
 	}
 	else if (declared_type == ANYELEMENTOID ||
 			 declared_type == ANYNONARRAYOID ||
-			 declared_type == ANYENUMOID ||
-			 declared_type == ANYRANGEOID)
+			 declared_type == ANYENUMOID)
 	{
 		if (context_declared_type == ANYARRAYOID)
 		{
@@ -2052,6 +2239,19 @@ resolve_generic_type(Oid declared_type,
 								"anyrange", format_type_be(context_base_type))));
 			return range_typelem;
 		}
+		else if (context_declared_type == ANYMULTIRANGEOID)
+		{
+			/* Use the element type corresponding to actual type */
+			Oid			context_base_type = getBaseType(context_actual_type);
+			Oid			multirange_typelem = get_multirange_subtype(context_base_type);
+
+			if (!OidIsValid(multirange_typelem))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("argument declared %s is not a multirange type but type %s",
+								"anymultirange", format_type_be(context_base_type))));
+			return multirange_typelem;
+		}
 		else if (context_declared_type == ANYELEMENTOID ||
 				 context_declared_type == ANYNONARRAYOID ||
 				 context_declared_type == ANYENUMOID)
@@ -2060,6 +2260,74 @@ resolve_generic_type(Oid declared_type,
 			return context_actual_type;
 		}
 	}
+	else if (declared_type == ANYRANGEOID)
+	{
+		if (context_declared_type == ANYRANGEOID)
+		{
+			Oid			context_base_type = getBaseType(context_actual_type);
+
+			if (!OidIsValid(context_base_type))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("argument declared %s is not a range type but type %s",
+								"anyrange", format_type_be(context_base_type))));
+			return context_base_type;
+		}
+		else if (context_declared_type == ANYMULTIRANGEOID)
+		{
+			Oid			context_base_type = getBaseType(context_actual_type);
+			Oid			multirange_typelem = get_multirange_subtype(context_base_type);
+
+			if (!OidIsValid(multirange_typelem))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("argument declared %s is not a multirange type but type %s",
+								"anymultirange", format_type_be(context_base_type))));
+			return multirange_typelem;
+		}
+		else
+		{
+			/* We can't infer a range from elem types */
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find range type for data type %s",
+							format_type_be(context_actual_type))));
+		}
+	}
+	else if (declared_type == ANYMULTIRANGEOID)
+	{
+		if (context_declared_type == ANYRANGEOID)
+		{
+			Oid			context_base_type = getBaseType(context_actual_type);
+			Oid			multirange_typeid = get_range_multirange(context_base_type);
+
+			if (!OidIsValid(multirange_typeid))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("argument declared %s is not a range type but type %s",
+								"anyrange", format_type_be(context_base_type))));
+			return multirange_typeid;
+		}
+		else if (context_declared_type == ANYMULTIRANGEOID)
+		{
+			Oid			context_base_type = getBaseType(context_actual_type);
+
+			if (!OidIsValid(context_base_type))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("argument declared %s is not a multirange type but type %s",
+								"anymultirange", format_type_be(context_base_type))));
+			return context_base_type;
+		}
+		else
+		{
+			/* We can't infer a multirange from elem types */
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(context_actual_type))));
+		}
+	}
 	else
 	{
 		/* declared_type isn't polymorphic, so return it as-is */
@@ -2174,6 +2442,11 @@ IsBinaryCoercible(Oid srctype, Oid targettype)
 		if (type_is_range(srctype))
 			return true;
 
+	/* Also accept any multirange type as coercible to ANMULTIYRANGE */
+	if (targettype == ANYMULTIRANGEOID)
+		if (type_is_multirange(srctype))
+			return true;
+
 	/* Also accept any composite type as coercible to RECORD */
 	if (targettype == RECORDOID)
 		if (ISCOMPLEX(srctype))
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 13efa9338c..7118dd0034 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -58,6 +58,7 @@ OBJS = \
 	mac.o \
 	mac8.o \
 	misc.o \
+	multirangetypes.o \
 	name.o \
 	network.o \
 	network_gist.o \
diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
new file mode 100644
index 0000000000..5323a6a635
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,966 @@
+/*-------------------------------------------------------------------------
+ *
+ * multirangetypes.c
+ *	  I/O functions, operators, and support functions for multirange types.
+ *
+ * The stored (serialized) format of a multirange value is:
+ *
+ *	4 bytes: varlena header
+ *	4 bytes: multirange type's OID
+ *	4 bytes: the number of ranges in the multirange
+ *	The range values, each maxaligned.
+ *
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/multirangetypes.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/tupmacs.h"
+#include "lib/stringinfo.h"
+#include "libpq/pqformat.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/date.h"
+#include "utils/hashutils.h"
+#include "utils/int8.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/timestamp.h"
+#include "utils/array.h"
+
+
+/* fn_extra cache entry for one of the range I/O functions */
+typedef struct MultirangeIOData
+{
+	TypeCacheEntry *typcache;	/* multirange type's typcache entry */
+	Oid			typiofunc;		/* range type's I/O function */
+	Oid			typioparam;		/* range type's I/O parameter */
+	FmgrInfo	proc;			/* lookup result for typiofunc */
+}			MultirangeIOData;
+
+typedef enum
+{
+	MULTIRANGE_BEFORE_RANGE,
+	MULTIRANGE_IN_RANGE,
+	MULTIRANGE_IN_RANGE_ESCAPED,
+	MULTIRANGE_IN_RANGE_QUOTED,
+	MULTIRANGE_IN_RANGE_QUOTED_ESCAPED,
+	MULTIRANGE_AFTER_RANGE,
+	MULTIRANGE_FINISHED,
+}			MultirangeParseState;
+
+static MultirangeIOData * get_multirange_io_data(FunctionCallInfo fcinfo, Oid rngtypid,
+												 IOFuncSelector func);
+static int32 multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range_count,
+									 RangeType **ranges);
+
+
+/*
+ *----------------------------------------------------------
+ * I/O FUNCTIONS
+ *----------------------------------------------------------
+ */
+
+/*
+ * Converts string to multirange.
+ *
+ * We expect curly brackets to bound the list,
+ * with zero or more ranges separated by commas.
+ * We accept whitespace anywhere:
+ * before/after our brackets and around the commas.
+ * Ranges can be the empty literal or some stuff inside parens/brackets.
+ * Mostly we delegate parsing the individual range contents
+ * to range_in, but we have to detect quoting and backslash-escaping
+ * which can happen for range bounds.
+ * Backslashes can escape something inside or outside a quoted string,
+ * and a quoted string can escape quote marks either either backslashes
+ * or double double-quotes.
+ */
+Datum
+multirange_in(PG_FUNCTION_ARGS)
+{
+	char	   *input_str = PG_GETARG_CSTRING(0);
+	Oid			mltrngtypoid = PG_GETARG_OID(1);
+	Oid			typmod = PG_GETARG_INT32(2);
+	TypeCacheEntry *rangetyp;
+	Oid			rngtypoid;
+	int32		ranges_seen = 0;
+	int32		range_count = 0;
+	int32		range_capacity = 8;
+	RangeType  *range;
+	RangeType **ranges = palloc(range_capacity * sizeof(RangeType *));
+	MultirangeIOData *cache;
+	MultirangeType *ret;
+	MultirangeParseState parse_state;
+	const char *ptr = input_str;
+	const char *range_str;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+	rngtypoid = rangetyp->type_id;
+
+	/* consume whitespace */
+	while (*ptr != '\0' && isspace((unsigned char) *ptr))
+		ptr++;
+
+	if (*ptr == '{')
+		ptr++;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("malformed multirange literal: \"%s\"",
+						input_str),
+				 errdetail("Missing left bracket.")));
+
+	/* consume ranges */
+	parse_state = MULTIRANGE_BEFORE_RANGE;
+	for (; parse_state != MULTIRANGE_FINISHED; ptr++)
+	{
+		char		ch = *ptr;
+
+		if (ch == '\0')
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+					 errmsg("malformed multirange literal: \"%s\"",
+							input_str),
+					 errdetail("Unexpected end of input.")));
+
+		 /* skip whitespace */ ;
+		if (isspace((unsigned char) ch))
+			continue;
+
+		switch (parse_state)
+		{
+			case MULTIRANGE_BEFORE_RANGE:
+				if (ch == '[' || ch == '(')
+				{
+					range_str = ptr;
+					parse_state = MULTIRANGE_IN_RANGE;
+				}
+				else if (ch == '}' && ranges_seen == 0)
+					parse_state = MULTIRANGE_FINISHED;
+				else if (pg_strncasecmp(ptr, RANGE_EMPTY_LITERAL,
+										strlen(RANGE_EMPTY_LITERAL)) == 0)
+				{
+					ranges_seen++;
+					/* nothing to do with an empty range */
+					ptr += strlen(RANGE_EMPTY_LITERAL) - 1;
+					parse_state = MULTIRANGE_AFTER_RANGE;
+				}
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+							 errmsg("malformed multirange literal: \"%s\"",
+									input_str),
+							 errdetail("Expected range start.")));
+				break;
+			case MULTIRANGE_IN_RANGE:
+				if (ch == '"')
+					parse_state = MULTIRANGE_IN_RANGE_QUOTED;
+				else if (ch == '\\')
+					parse_state = MULTIRANGE_IN_RANGE_ESCAPED;
+				else if (ch == ']' || ch == ')')
+				{
+					range_str_len = ptr - range_str + 2;
+					range_str_copy = palloc0(range_str_len);
+					strlcpy(range_str_copy, range_str, range_str_len);
+					if (range_capacity == range_count)
+					{
+						range_capacity *= 2;
+						ranges = (RangeType **) repalloc(ranges,
+														 range_capacity * sizeof(RangeType *));
+					}
+					ranges_seen++;
+					range = DatumGetRangeTypeP(
+											   InputFunctionCall(&cache->proc, range_str_copy,
+																 cache->typioparam, typmod));
+					if (!RangeIsEmpty(range))
+						ranges[range_count++] = range;
+					parse_state = MULTIRANGE_AFTER_RANGE;
+				}
+				else
+					 /* include it in range_str */ ;
+				break;
+			case MULTIRANGE_IN_RANGE_ESCAPED:
+				/* include it in range_str */
+				parse_state = MULTIRANGE_IN_RANGE;
+				break;
+			case MULTIRANGE_IN_RANGE_QUOTED:
+				if (ch == '"')
+					if (*(ptr + 1) == '"')
+					{
+						/* two quote marks means an escaped quote mark */
+						ptr++;
+					}
+					else
+						parse_state = MULTIRANGE_IN_RANGE;
+				else if (ch == '\\')
+					parse_state = MULTIRANGE_IN_RANGE_QUOTED_ESCAPED;
+				else
+					 /* include it in range_str */ ;
+				break;
+			case MULTIRANGE_AFTER_RANGE:
+				if (ch == ',')
+					parse_state = MULTIRANGE_BEFORE_RANGE;
+				else if (ch == '}')
+					parse_state = MULTIRANGE_FINISHED;
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+							 errmsg("malformed multirange literal: \"%s\"",
+									input_str),
+							 errdetail("Expected comma or end of multirange.")));
+				break;
+			case MULTIRANGE_IN_RANGE_QUOTED_ESCAPED:
+				/* include it in range_str */
+				parse_state = MULTIRANGE_IN_RANGE_QUOTED;
+				break;
+			default:
+				elog(ERROR, "Unknown parse state: %d", parse_state);
+		}
+	}
+
+	/* consume whitespace */
+	while (*ptr != '\0' && isspace((unsigned char) *ptr))
+		ptr++;
+
+	if (*ptr != '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("malformed multirange literal: \"%s\"",
+						input_str),
+				 errdetail("Junk after right bracket.")));
+
+	ret = make_multirange(mltrngtypoid, rangetyp, range_count, ranges);
+	PG_RETURN_MULTIRANGE_P(ret);
+}
+
+Datum
+multirange_out(PG_FUNCTION_ARGS)
+{
+	MultirangeType *multirange = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(multirange);
+	MultirangeIOData *cache;
+	StringInfoData buf;
+	RangeType  *range;
+	char	   *rangeStr;
+	Pointer		ptr = (char *) multirange;
+	Pointer		end = ptr + VARSIZE(multirange);
+	int32		range_count = 0;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_output);
+
+	initStringInfo(&buf);
+
+	appendStringInfoChar(&buf, '{');
+
+	ptr = (char *) MAXALIGN(multirange + 1);
+	while (ptr < end)
+	{
+		if (range_count > 0)
+			appendStringInfoChar(&buf, ',');
+		range = (RangeType *) ptr;
+		rangeStr = OutputFunctionCall(&cache->proc, RangeTypePGetDatum(range));
+		appendStringInfoString(&buf, rangeStr);
+		ptr += MAXALIGN(VARSIZE(range));
+		range_count++;
+	}
+
+	appendStringInfoChar(&buf, '}');
+
+	PG_RETURN_CSTRING(buf.data);
+}
+
+/*
+ * Binary representation: The first byte is the flags, then the lower bound
+ * (if present), then the upper bound (if present).  Each bound is represented
+ * by a 4-byte length header and the binary representation of that bound (as
+ * returned by a call to the send function for the subtype).
+ */
+
+Datum
+multirange_recv(PG_FUNCTION_ARGS)
+{
+	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
+	Oid			mltrngtypoid = PG_GETARG_OID(1);
+	int32		typmod = PG_GETARG_INT32(2);
+	MultirangeIOData *cache;
+	TypeCacheEntry *rangetyp;
+	uint32		range_count;
+	RangeType **ranges;
+	MultirangeType *ret;
+	int			i;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive);
+	rangetyp = cache->typcache->rngtype;
+
+	range_count = pq_getmsgint(buf, 4);
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+	{
+		uint32		range_len = pq_getmsgint(buf, 4);
+		const char *range_data = pq_getmsgbytes(buf, range_len);
+		StringInfoData range_buf;
+
+		initStringInfo(&range_buf);
+		appendBinaryStringInfo(&range_buf, range_data, range_len);
+
+		ranges[i] = DatumGetRangeTypeP(
+									   ReceiveFunctionCall(&cache->proc,
+														   &range_buf,
+														   cache->typioparam,
+														   typmod));
+		pfree(range_buf.data);
+	}
+
+	pq_getmsgend(buf);
+
+	ret = make_multirange(mltrngtypoid, rangetyp, range_count, ranges);
+	PG_RETURN_MULTIRANGE_P(ret);
+}
+
+Datum
+multirange_send(PG_FUNCTION_ARGS)
+{
+	MultirangeType	   *multirange = PG_GETARG_MULTIRANGE_P(0);
+	Oid					mltrngtypoid = MultirangeTypeGetOid(multirange);
+	StringInfo			buf = makeStringInfo();
+	RangeType		  **ranges;
+	int32				range_count;
+	int32				i;
+	MultirangeIOData	*cache;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_send);
+
+	/* construct output */
+	pq_begintypsend(buf);
+
+	pq_sendint32(buf, multirange->rangeCount);
+
+	multirange_deserialize(multirange, &range_count, &ranges);
+	for (i = 0; i < range_count; i++)
+	{
+		Datum		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->proc, range));
+		uint32		range_len = VARSIZE(range) - VARHDRSZ;
+		char	   *range_data = VARDATA(range);
+
+		pq_sendint32(buf, range_len);
+		pq_sendbytes(buf, range_data, range_len);
+	}
+
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
+}
+
+Datum
+multirange_typanalyze(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(true);
+}
+
+/*
+ * get_multirange_io_data: get cached information needed for multirange type I/O
+ *
+ * The multirange I/O functions need a bit more cached info than other multirange
+ * functions, so they store a MultirangeIOData struct in fn_extra, not just a
+ * pointer to a type cache entry.
+ */
+static MultirangeIOData *
+get_multirange_io_data(FunctionCallInfo fcinfo, Oid mltrngtypid, IOFuncSelector func)
+{
+	MultirangeIOData *cache = (MultirangeIOData *) fcinfo->flinfo->fn_extra;
+
+	if (cache == NULL || cache->typcache->type_id != mltrngtypid)
+	{
+		int16		typlen;
+		bool		typbyval;
+		char		typalign;
+		char		typdelim;
+
+		cache = (MultirangeIOData *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+														sizeof(MultirangeIOData));
+		cache->typcache = lookup_type_cache(mltrngtypid, TYPECACHE_MULTIRANGE_INFO);
+		if (cache->typcache->rngtype == NULL)
+			elog(ERROR, "type %u is not a multirange type", mltrngtypid);
+
+		/* get_type_io_data does more than we need, but is convenient */
+		get_type_io_data(cache->typcache->rngtype->type_id,
+						 func,
+						 &typlen,
+						 &typbyval,
+						 &typalign,
+						 &typdelim,
+						 &cache->typioparam,
+						 &cache->typiofunc);
+
+		if (!OidIsValid(cache->typiofunc))
+		{
+			/* this could only happen for receive or send */
+			if (func == IOFunc_receive)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_FUNCTION),
+						 errmsg("no binary input function available for type %s",
+								format_type_be(cache->typcache->rngtype->type_id))));
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_FUNCTION),
+						 errmsg("no binary output function available for type %s",
+								format_type_be(cache->typcache->rngtype->type_id))));
+		}
+		fmgr_info_cxt(cache->typiofunc, &cache->proc,
+					  fcinfo->flinfo->fn_mcxt);
+
+		fcinfo->flinfo->fn_extra = (void *) cache;
+	}
+
+	return cache;
+}
+
+/*
+ *----------------------------------------------------------
+ * SUPPORT FUNCTIONS
+ *
+ *	 These functions aren't in pg_proc, but are useful for
+ *	 defining new generic multirange functions in C.
+ *----------------------------------------------------------
+ */
+
+/*
+ * multirange_get_typcache: get cached information about a multirange type
+ *
+ * This is for use by multirange-related functions that follow the convention
+ * of using the fn_extra field as a pointer to the type cache entry for
+ * the multirange type.  Functions that need to cache more information than
+ * that must fend for themselves.
+ */
+TypeCacheEntry *
+multirange_get_typcache(FunctionCallInfo fcinfo, Oid mltrngtypid)
+{
+	TypeCacheEntry *typcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+
+	if (typcache == NULL ||
+		typcache->type_id != mltrngtypid)
+	{
+		typcache = lookup_type_cache(mltrngtypid, TYPECACHE_MULTIRANGE_INFO);
+		if (typcache->rngtype == NULL)
+			elog(ERROR, "type %u is not a multirange type", mltrngtypid);
+		fcinfo->flinfo->fn_extra = (void *) typcache;
+	}
+
+	return typcache;
+}
+
+/*
+ * This serializes the multirange from a list of non-null ranges.
+ * It also sorts the ranges and merges any that touch.
+ * The ranges should already be detoasted, and there should be no NULLs.
+ * This should be used by most callers.
+ *
+ * Note that we may change the `ranges` parameter (the pointers, but not
+ * any already-existing RangeType contents).
+ */
+MultirangeType *
+make_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp, int32 range_count,
+				RangeType **ranges)
+{
+	MultirangeType *multirange;
+	RangeType  *range;
+	int			i;
+	int32		bytelen;
+	Pointer		ptr;
+
+	/* Sort and merge input ranges. */
+	range_count = multirange_canonicalize(rangetyp, range_count, ranges);
+
+	/*
+	 * Count space for varlena header, multirange type's OID, other fields,
+	 * and padding so that RangeTypes start aligned.
+	 */
+	bytelen = MAXALIGN(sizeof(MultirangeType));
+
+	/* Count space for all ranges */
+	for (i = 0; i < range_count; i++)
+	{
+		range = ranges[i];
+		bytelen += MAXALIGN(VARSIZE(range));
+	}
+
+	/* Note: zero-fill is required here, just as in heap tuples */
+	multirange = palloc0(bytelen);
+	SET_VARSIZE(multirange, bytelen);
+
+	/* Now fill in the datum */
+	multirange->multirangetypid = mltrngtypoid;
+	multirange->rangeCount = range_count;
+
+	ptr = (char *) MAXALIGN(multirange + 1);
+	for (i = 0; i < range_count; i++)
+	{
+		range = ranges[i];
+		memcpy(ptr, range, VARSIZE(range));
+		ptr += MAXALIGN(VARSIZE(range));
+	}
+
+	return multirange;
+}
+
+/*
+ * Converts a list of any ranges you like into a list that is sorted and merged.
+ * Changes the contents of `ranges`.
+ * Returns the number of slots actually used,
+ * which may be less than input_range_count but never more.
+ * We assume that no input ranges are null, but empties are okay.
+ */
+static int32
+multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range_count,
+						RangeType **ranges)
+{
+	RangeType  *lastRange = NULL;
+	RangeType  *currentRange;
+	int32		i;
+	int32		output_range_count = 0;
+
+	/* Sort the ranges so we can find the ones that overlap/meet. */
+	qsort_arg(ranges, input_range_count, sizeof(RangeType *), range_compare,
+			  rangetyp);
+
+	/* Now merge where possible: */
+	for (i = 0; i < input_range_count; i++)
+	{
+		currentRange = ranges[i];
+		if (RangeIsEmpty(currentRange))
+			continue;
+
+		if (lastRange == NULL)
+		{
+			ranges[output_range_count++] = lastRange = currentRange;
+			continue;
+		}
+
+		/*
+		 * range_adjacent_internal gives true if *either* A meets B or B meets
+		 * A, which is not quite want we want, but we rely on the sorting
+		 * above to rule out B meets A ever happening.
+		 */
+		if (range_adjacent_internal(rangetyp, lastRange, currentRange))
+		{
+			/* The two ranges touch (without overlap), so merge them: */
+			ranges[output_range_count - 1] = lastRange =
+				range_union_internal(rangetyp, lastRange, currentRange, false);
+		}
+		else if (range_before_internal(rangetyp, lastRange, currentRange))
+		{
+			/* There's a gap, so make a new entry: */
+			lastRange = ranges[output_range_count] = currentRange;
+			output_range_count++;
+		}
+		else
+		{
+			/* They must overlap, so merge them: */
+			ranges[output_range_count - 1] = lastRange =
+				range_union_internal(rangetyp, lastRange, currentRange, true);
+		}
+	}
+
+	return output_range_count;
+}
+
+/*
+ * multirange_deserialize: deconstruct a multirange value
+ *
+ * NB: the given multirange object must be fully detoasted; it cannot have a
+ * short varlena header.
+ */
+void
+multirange_deserialize(MultirangeType * multirange,
+					   int32 *range_count, RangeType ***ranges)
+{
+	RangeType  *r;
+	Pointer		ptr;
+	Pointer		end;
+	int32		i;
+
+	*range_count = multirange->rangeCount;
+	if (*range_count == 0)
+	{
+		ranges = NULL;
+		return;
+	}
+
+	*ranges = palloc0(*range_count * sizeof(RangeType *));
+
+	ptr = (char *) multirange;
+	end = ptr + VARSIZE(multirange);
+	ptr = (char *) MAXALIGN(multirange + 1);
+	i = 0;
+	while (ptr < end)
+	{
+		r = (RangeType *) ptr;
+		(*ranges)[i++] = r;
+
+		ptr += MAXALIGN(VARSIZE(r));
+	}
+}
+
+MultirangeType *
+make_empty_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp)
+{
+	return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+}
+
+/*
+ *----------------------------------------------------------
+ * GENERIC FUNCTIONS
+ *----------------------------------------------------------
+ */
+
+/*
+ * Construct multirange value from zero or more ranges.
+ * Since this is a variadic function we get passed an array.
+ * The array must contain range types that match our return value,
+ * and there must be no NULLs.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	ArrayType  *rangeArray;
+	int			range_count;
+	Datum	   *elements;
+	bool	   *nulls;
+	RangeType **ranges;
+	int			dims;
+	int			i;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * A no-arg invocation should call multirange_constructor0 instead, but
+	 * returning an empty range is what that does.
+	 */
+
+	if (PG_NARGS() == 0)
+		PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 0, NULL));
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+		ereport(ERROR, (errmsg("Can't construct multirange with a NULL input")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR, (errmsg("Can't construct multirange with a multi-dimensional array")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR, (errmsg("type %u does not match constructor type", rngtypid)));
+
+	/*
+	 * Be careful: we can still be called with zero ranges, like this:
+	 * `int4multirange(variadic '{}'::int4range[])
+	 */
+	if (dims == 0)
+	{
+		range_count = 0;
+		ranges = NULL;
+	}
+	else
+	{
+		deconstruct_array(rangeArray, rngtypid, rangetyp->typlen, rangetyp->typbyval,
+						  rangetyp->typalign, &elements, &nulls, &range_count);
+
+		ranges = palloc0(range_count * sizeof(RangeType *));
+		for (i = 0; i < range_count; i++)
+		{
+			if (nulls[i])
+				ereport(ERROR, (errmsg("Can't construct multirange with a NULL element")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Constructor just like multirange_constructor1,
+ * but opr_sanity gets angry if the same internal function
+ * handles multiple functions with different arg counts.
+ */
+Datum
+multirange_constructor0(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/* We should always be called with no arguments */
+
+	if (PG_NARGS() == 0)
+		PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 0, NULL));
+	else
+		ereport(ERROR, (errmsg("Zero-param multirange constructor shouldn't have arguments")));
+}
+
+
+/* multirange, multirange -> bool functions */
+
+/* equality (internal version) */
+bool
+multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType * mr1, MultirangeType * mr2)
+{
+	int32		range_count_1;
+	int32		range_count_2;
+	int32		i;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/* Different types should be prevented by ANYMULTIRANGE matching rules */
+	if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2))
+		elog(ERROR, "multirange types do not match");
+
+	multirange_deserialize(mr1, &range_count_1, &ranges1);
+	multirange_deserialize(mr2, &range_count_2, &ranges2);
+
+	if (range_count_1 != range_count_2)
+		return false;
+
+	for (i = 0; i < range_count_1; i++)
+	{
+		r1 = ranges1[i];
+		r2 = ranges2[i];
+
+		if (!range_eq_internal(typcache->rngtype, r1, r2))
+			return false;
+	}
+
+	return true;
+}
+
+/* equality */
+Datum
+multirange_eq(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	PG_RETURN_BOOL(multirange_eq_internal(typcache, mr1, mr2));
+}
+
+/* inequality (internal version) */
+bool
+multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType * mr1, MultirangeType * mr2)
+{
+	return (!multirange_eq_internal(typcache, mr1, mr2));
+}
+
+/* inequality */
+Datum
+multirange_ne(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	PG_RETURN_BOOL(multirange_ne_internal(typcache, mr1, mr2));
+}
+
+/* Btree support */
+
+/* btree comparator */
+Datum
+multirange_cmp(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	int32		range_count_1;
+	int32		range_count_2;
+	int32		range_count_max;
+	int32		i;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	TypeCacheEntry *typcache;
+	int			cmp = 0;		/* If both are empty we'll use this. */
+
+	/* Different types should be prevented by ANYMULTIRANGE matching rules */
+	if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2))
+		elog(ERROR, "multirange types do not match");
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count_1, &ranges1);
+	multirange_deserialize(mr2, &range_count_2, &ranges2);
+
+	/* Loop over source data */
+	range_count_max = Max(range_count_1, range_count_2);
+	for (i = 0; i < range_count_max; i++)
+	{
+		/*
+		 * If one multirange is shorter, it's as if it had empty ranges at the
+		 * end to extend its length. An empty range compares earlier than any
+		 * other range, so the shorter multirange comes before the longer.
+		 * This is the same behavior as in other types, e.g. in strings 'aaa'
+		 * < 'aaaaaa'.
+		 */
+		if (i >= range_count_1)
+		{
+			cmp = -1;
+			break;
+		}
+		if (i >= range_count_2)
+		{
+			cmp = 1;
+			break;
+		}
+		r1 = ranges1[i];
+		r2 = ranges2[i];
+
+		cmp = range_cmp_internal(typcache->rngtype, r1, r2);
+		if (cmp != 0)
+			break;
+	}
+
+	PG_FREE_IF_COPY(mr1, 0);
+	PG_FREE_IF_COPY(mr2, 1);
+
+	PG_RETURN_INT32(cmp);
+}
+
+/* inequality operators using the multirange_cmp function */
+Datum
+multirange_lt(PG_FUNCTION_ARGS)
+{
+	int			cmp = multirange_cmp(fcinfo);
+
+	PG_RETURN_BOOL(cmp < 0);
+}
+
+Datum
+multirange_le(PG_FUNCTION_ARGS)
+{
+	int			cmp = multirange_cmp(fcinfo);
+
+	PG_RETURN_BOOL(cmp <= 0);
+}
+
+Datum
+multirange_ge(PG_FUNCTION_ARGS)
+{
+	int			cmp = multirange_cmp(fcinfo);
+
+	PG_RETURN_BOOL(cmp >= 0);
+}
+
+Datum
+multirange_gt(PG_FUNCTION_ARGS)
+{
+	int			cmp = multirange_cmp(fcinfo);
+
+	PG_RETURN_BOOL(cmp > 0);
+}
+
+/* Hash support */
+
+/* hash a multirange value */
+Datum
+hash_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	uint32		result = 1;
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	int32		i;
+	RangeType  *r;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+	for (i = 0; i < range_count; i++)
+	{
+		uint32		elthash;
+
+		r = ranges[i];
+		elthash = hash_range_internal(typcache->rngtype, r);
+
+		/*
+		 * Use the same approach as hash_array to combine the individual
+		 * elements' hash values:
+		 */
+		result = (result << 5) - result + elthash;
+	}
+
+	PG_FREE_IF_COPY(mr, 0);
+
+	PG_RETURN_UINT32(result);
+}
+
+/*
+ * Returns 64-bit value by hashing a value to a 64-bit value, with a seed.
+ * Otherwise, similar to hash_multirange.
+ */
+Datum
+hash_multirange_extended(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		seed = PG_GETARG_DATUM(1);
+	uint64		result = 1;
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	int32		i;
+	RangeType  *r;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+	for (i = 0; i < range_count; i++)
+	{
+		uint64		elthash;
+
+		r = ranges[i];
+		elthash = hash_range_extended_internal(typcache->rngtype, r, seed);
+
+		/*
+		 * Use the same approach as hash_array to combine the individual
+		 * elements' hash values:
+		 */
+		result = (result << 5) - result + elthash;
+	}
+
+	PG_FREE_IF_COPY(mr, 0);
+
+	PG_RETURN_UINT64(result);
+}
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 5c886cfe96..94484b3373 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -26,6 +26,7 @@
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
 
 
 /*
@@ -184,6 +185,30 @@ anyrange_out(PG_FUNCTION_ARGS)
 	return range_out(fcinfo);
 }
 
+/*
+ * anymultirange_in		- input routine for pseudo-type ANYMULTIRANGE.
+ */
+Datum
+anymultirange_in(PG_FUNCTION_ARGS)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("cannot accept a value of type %s", "anymultirange")));
+
+	PG_RETURN_VOID();			/* keep compiler quiet */
+}
+
+/*
+ * anymultirange_out		- output routine for pseudo-type ANYMULTIRANGE.
+ *
+ * We may as well allow this, since multirange_out will in fact work.
+ */
+Datum
+anymultirange_out(PG_FUNCTION_ARGS)
+{
+	return multirange_out(fcinfo);
+}
+
 /*
  * void_in		- input routine for pseudo-type VOID.
  *
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 461c428413..65f68614c1 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -43,8 +43,6 @@
 #include "utils/timestamp.h"
 
 
-#define RANGE_EMPTY_LITERAL "empty"
-
 /* fn_extra cache entry for one of the range I/O functions */
 typedef struct RangeIOData
 {
@@ -1177,12 +1175,68 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
+	return cmp;
+}
+
+/* btree comparator */
+Datum
+range_cmp(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int			cmp;
+
+	check_stack_depth();		/* recurses when subtype is a range type */
+
+	/* Different types should be prevented by ANYRANGE matching rules */
+	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
+		elog(ERROR, "range types do not match");
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	cmp = range_cmp_internal(typcache, r1, r2);
+
 	PG_FREE_IF_COPY(r1, 0);
 	PG_FREE_IF_COPY(r2, 1);
 
 	PG_RETURN_INT32(cmp);
 }
 
+/*
+ * Internal version of range_cmp
+ */
+int
+range_cmp_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+	int			cmp;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	/* For b-tree use, empty ranges sort before all else */
+	if (empty1 && empty2)
+		cmp = 0;
+	else if (empty1)
+		cmp = -1;
+	else if (empty2)
+		cmp = 1;
+	else
+	{
+		cmp = range_cmp_bounds(typcache, &lower1, &lower2);
+		if (cmp == 0)
+			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
+	}
+
+	return cmp;
+}
+
 /* inequality operators using the range_cmp function */
 Datum
 range_lt(PG_FUNCTION_ARGS)
@@ -1218,13 +1272,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1284,6 @@ hash_range(PG_FUNCTION_ARGS)
 	uint32		lower_hash;
 	uint32		upper_hash;
 
-	check_stack_depth();		/* recurses when subtype is a range type */
-
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r));
-
 	/* deserialize */
 	range_deserialize(typcache, r, &lower, &upper, &empty);
 	flags = range_get_flags(r);
@@ -1278,20 +1325,27 @@ hash_range(PG_FUNCTION_ARGS)
 	result = (result << 1) | (result >> 31);
 	result ^= upper_hash;
 
-	PG_RETURN_INT32(result);
+	return result;
 }
 
-/*
- * Returns 64-bit value by hashing a value to a 64-bit value, with a seed.
- * Otherwise, similar to hash_range.
- */
+/* hash a range value */
 Datum
-hash_range_extended(PG_FUNCTION_ARGS)
+hash_range(PG_FUNCTION_ARGS)
 {
 	RangeType  *r = PG_GETARG_RANGE_P(0);
-	Datum		seed = PG_GETARG_DATUM(1);
-	uint64		result;
 	TypeCacheEntry *typcache;
+
+	check_stack_depth();		/* recurses when subtype is a range type */
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r));
+
+	PG_RETURN_INT32(hash_range_internal(typcache, r));
+}
+
+uint64
+hash_range_extended_internal(TypeCacheEntry *typcache, RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1354,6 @@ hash_range_extended(PG_FUNCTION_ARGS)
 	uint64		lower_hash;
 	uint64		upper_hash;
 
-	check_stack_depth();
-
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r));
-
 	range_deserialize(typcache, r, &lower, &upper, &empty);
 	flags = range_get_flags(r);
 
@@ -1342,7 +1392,25 @@ hash_range_extended(PG_FUNCTION_ARGS)
 	result = ROTATE_HIGH_AND_LOW_32BITS(result);
 	result ^= upper_hash;
 
-	PG_RETURN_UINT64(result);
+	return result;
+}
+
+/*
+ * Returns 64-bit value by hashing a value to a 64-bit value, with a seed.
+ * Otherwise, similar to hash_range.
+ */
+Datum
+hash_range_extended(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	Datum		seed = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	check_stack_depth();
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r));
+
+	PG_RETURN_UINT64(hash_range_extended_internal(typcache, r, seed));
 }
 
 /*
@@ -1937,6 +2005,45 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 										   b1->val, b2->val));
 }
 
+/*
+ * Compares two ranges so we can qsort them.
+ * This expects that you give qsort a RangeType **,
+ * so the RangeTypes can be in diverse locations,
+ * as long as you have a list of pointers to them all.
+ */
+int
+range_compare(const void *key1, const void *key2, void *arg)
+{
+	RangeType  *r1 = *(RangeType **) key1;
+	RangeType  *r2 = *(RangeType **) key2;
+	TypeCacheEntry *typcache = (TypeCacheEntry *) arg;
+	RangeBound	lower1;
+	RangeBound	upper1;
+	RangeBound	lower2;
+	RangeBound	upper2;
+	bool		empty1;
+	bool		empty2;
+	int			cmp;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (empty1 && empty2)
+		cmp = 0;
+	else if (empty1)
+		cmp = -1;
+	else if (empty2)
+		cmp = 1;
+	else
+	{
+		cmp = range_cmp_bounds(typcache, &lower1, &lower2);
+		if (cmp == 0)
+			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
+	}
+
+	return cmp;
+}
+
 /*
  * Build an empty range value of the type indicated by the typcache entry.
  */
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 27602fa49c..360a71427e 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2468,6 +2468,16 @@ type_is_range(Oid typid)
 	return (get_typtype(typid) == TYPTYPE_RANGE);
 }
 
+/*
+ * type_is_multirange
+ *	  Returns true if the given type is a multirange type.
+ */
+bool
+type_is_multirange(Oid typid)
+{
+	return (get_typtype(typid) == TYPTYPE_MULTIRANGE);
+}
+
 /*
  * get_type_category_preferred
  *
@@ -3150,6 +3160,58 @@ get_range_subtype(Oid rangeOid)
 		return InvalidOid;
 }
 
+/*
+ * get_range_multirange
+ *		Returns the multirange type of a given range type
+ *
+ * Returns InvalidOid if the type is not a range type.
+ */
+Oid
+get_range_multirange(Oid rangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGETYPE, ObjectIdGetDatum(rangeOid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_range rngtup = (Form_pg_range) GETSTRUCT(tp);
+		Oid			result;
+
+		result = rngtup->mltrngtypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*				---------- PG_MULTIRANGE CACHE ----------				 */
+
+/*
+ * get_multirange_subtype
+ *		Returns the subtype of a given multirange type
+ *
+ * Returns InvalidOid if the type is not a multirange type.
+ */
+Oid
+get_multirange_subtype(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(MULTIRANGETYPE, ObjectIdGetDatum(multirangeOid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_range rngtup = (Form_pg_range) GETSTRUCT(tp);
+		Oid			result;
+
+		result = rngtup->rngtypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
 /*				---------- PG_INDEX CACHE ----------				 */
 
 /*
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d69c0ff813..6156ab72a1 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -508,6 +508,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		4
 	},
+	{RangeRelationId,			/* MULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_mltrngtypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
 	{NamespaceRelationId,		/* NAMESPACENAME */
 		NamespaceNameIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 11920db0d9..0630687b49 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -278,6 +278,7 @@ static uint64 tupledesc_id_counter = INVALID_TUPLEDESC_IDENTIFIER;
 
 static void load_typcache_tupdesc(TypeCacheEntry *typentry);
 static void load_rangetype_info(TypeCacheEntry *typentry);
+static void load_multirangetype_info(TypeCacheEntry *typentry);
 static void load_domaintype_info(TypeCacheEntry *typentry);
 static int	dcs_cmp(const void *a, const void *b);
 static void decr_dcc_refcount(DomainConstraintCache *dcc);
@@ -294,6 +295,9 @@ static void cache_record_field_properties(TypeCacheEntry *typentry);
 static bool range_element_has_hashing(TypeCacheEntry *typentry);
 static bool range_element_has_extended_hashing(TypeCacheEntry *typentry);
 static void cache_range_element_properties(TypeCacheEntry *typentry);
+static bool multirange_element_has_hashing(TypeCacheEntry *typentry);
+static bool multirange_element_has_extended_hashing(TypeCacheEntry *typentry);
+static void cache_multirange_element_properties(TypeCacheEntry *typentry);
 static void TypeCacheRelCallback(Datum arg, Oid relid);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheConstrCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -500,8 +504,8 @@ lookup_type_cache(Oid type_id, int flags)
 		 * to see if the element type or column types support equality.  If
 		 * not, array_eq or record_eq would fail at runtime, so we don't want
 		 * to report that the type has equality.  (We can omit similar
-		 * checking for ranges because ranges can't be created in the first
-		 * place unless their subtypes support equality.)
+		 * checking for ranges and multiranges because ranges can't be created
+		 * in the first place unless their subtypes support equality.)
 		 */
 		if (eq_opr == ARRAY_EQ_OP &&
 			!array_element_has_equality(typentry))
@@ -538,7 +542,7 @@ lookup_type_cache(Oid type_id, int flags)
 
 		/*
 		 * As above, make sure array_cmp or record_cmp will succeed; but again
-		 * we need no special check for ranges.
+		 * we need no special check for ranges or multiranges.
 		 */
 		if (lt_opr == ARRAY_LT_OP &&
 			!array_element_has_compare(typentry))
@@ -563,7 +567,7 @@ lookup_type_cache(Oid type_id, int flags)
 
 		/*
 		 * As above, make sure array_cmp or record_cmp will succeed; but again
-		 * we need no special check for ranges.
+		 * we need no special check for ranges or multiranges.
 		 */
 		if (gt_opr == ARRAY_GT_OP &&
 			!array_element_has_compare(typentry))
@@ -588,7 +592,7 @@ lookup_type_cache(Oid type_id, int flags)
 
 		/*
 		 * As above, make sure array_cmp or record_cmp will succeed; but again
-		 * we need no special check for ranges.
+		 * we need no special check for ranges or multiranges.
 		 */
 		if (cmp_proc == F_BTARRAYCMP &&
 			!array_element_has_compare(typentry))
@@ -640,6 +644,13 @@ lookup_type_cache(Oid type_id, int flags)
 			!range_element_has_hashing(typentry))
 			hash_proc = InvalidOid;
 
+		/*
+		 * Likewise for hash_multirange.
+		 */
+		if (hash_proc == F_HASH_MULTIRANGE &&
+			!multirange_element_has_hashing(typentry))
+			hash_proc = InvalidOid;
+
 		/* Force update of hash_proc_finfo only if we're changing state */
 		if (typentry->hash_proc != hash_proc)
 			typentry->hash_proc_finfo.fn_oid = InvalidOid;
@@ -684,6 +695,13 @@ lookup_type_cache(Oid type_id, int flags)
 			!range_element_has_extended_hashing(typentry))
 			hash_extended_proc = InvalidOid;
 
+		/*
+		 * Likewise for hash_multirange_extended.
+		 */
+		if (hash_extended_proc == F_HASH_MULTIRANGE_EXTENDED &&
+			!multirange_element_has_extended_hashing(typentry))
+			hash_extended_proc = InvalidOid;
+
 		/* Force update of proc finfo only if we're changing state */
 		if (typentry->hash_extended_proc != hash_extended_proc)
 			typentry->hash_extended_proc_finfo.fn_oid = InvalidOid;
@@ -758,6 +776,16 @@ lookup_type_cache(Oid type_id, int flags)
 		load_rangetype_info(typentry);
 	}
 
+	/*
+	 * If requested, get information about a multirange type
+	 */
+	if ((flags & TYPECACHE_MULTIRANGE_INFO) &&
+		typentry->rngtype == NULL &&
+		typentry->typtype == TYPTYPE_MULTIRANGE)
+	{
+		load_multirangetype_info(typentry);
+	}
+
 	/*
 	 * If requested, get information about a domain type
 	 */
@@ -870,6 +898,33 @@ load_rangetype_info(TypeCacheEntry *typentry)
 }
 
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Form_pg_range pg_range;
+	HeapTuple	tup;
+	Oid			rangetypeOid;
+
+	/* get information from pg_range */
+	tup = SearchSysCache1(MULTIRANGETYPE, ObjectIdGetDatum(typentry->type_id));
+	/* should not fail, since we already checked typtype ... */
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+	pg_range = (Form_pg_range) GETSTRUCT(tup);
+
+	rangetypeOid = pg_range->rngtypid;
+
+	ReleaseSysCache(tup);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
+
+
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
  *
@@ -1469,11 +1524,11 @@ cache_record_field_properties(TypeCacheEntry *typentry)
 }
 
 /*
- * Likewise, some helper functions for range types.
+ * Likewise, some helper functions for range and multirange types.
  *
  * We can borrow the flag bits for array element properties to use for range
  * element properties, since those flag bits otherwise have no use in a
- * range type's typcache entry.
+ * range or multirange type's typcache entry.
  */
 
 static bool
@@ -1516,6 +1571,46 @@ cache_range_element_properties(TypeCacheEntry *typentry)
 	typentry->flags |= TCFLAGS_CHECKED_ELEM_PROPERTIES;
 }
 
+static bool
+multirange_element_has_hashing(TypeCacheEntry *typentry)
+{
+	if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES))
+		cache_multirange_element_properties(typentry);
+	return (typentry->flags & TCFLAGS_HAVE_ELEM_HASHING) != 0;
+}
+
+static bool
+multirange_element_has_extended_hashing(TypeCacheEntry *typentry)
+{
+	if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES))
+		cache_multirange_element_properties(typentry);
+	return (typentry->flags & TCFLAGS_HAVE_ELEM_EXTENDED_HASHING) != 0;
+}
+
+static void
+cache_multirange_element_properties(TypeCacheEntry *typentry)
+{
+	/* load up range link if we didn't already */
+	if (typentry->rngtype == NULL &&
+		typentry->typtype == TYPTYPE_MULTIRANGE)
+		load_multirangetype_info(typentry);
+
+	if (typentry->rngtype != NULL && typentry->rngtype->rngelemtype != NULL)
+	{
+		TypeCacheEntry *elementry;
+
+		/* might need to calculate subtype's hash function properties */
+		elementry = lookup_type_cache(typentry->rngtype->rngelemtype->type_id,
+									  TYPECACHE_HASH_PROC |
+									  TYPECACHE_HASH_EXTENDED_PROC);
+		if (OidIsValid(elementry->hash_proc))
+			typentry->flags |= TCFLAGS_HAVE_ELEM_HASHING;
+		if (OidIsValid(elementry->hash_extended_proc))
+			typentry->flags |= TCFLAGS_HAVE_ELEM_EXTENDED_HASHING;
+	}
+	typentry->flags |= TCFLAGS_CHECKED_ELEM_PROPERTIES;
+}
+
 /*
  * Make sure that RecordCacheArray and RecordIdentifierArray are large enough
  * to store 'typmod'.
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 4688fbc50c..d65968d4eb 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -470,11 +470,13 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	bool		have_anyelement_result = false;
 	bool		have_anyarray_result = false;
 	bool		have_anyrange_result = false;
+	bool		have_anymultirange_result = false;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	Oid			anyelement_type = InvalidOid;
 	Oid			anyarray_type = InvalidOid;
 	Oid			anyrange_type = InvalidOid;
+	Oid			anymultirange_type = InvalidOid;
 	Oid			anycollation = InvalidOid;
 	int			i;
 
@@ -500,12 +502,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 			case ANYRANGEOID:
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_anymultirange_result = true;
+				break;
 			default:
 				break;
 		}
 	}
 	if (!have_anyelement_result && !have_anyarray_result &&
-		!have_anyrange_result)
+		!have_anyrange_result && !have_anymultirange_result)
 		return true;
 
 	/*
@@ -533,6 +538,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				if (!OidIsValid(anyrange_type))
 					anyrange_type = get_call_expr_argtype(call_expr, i);
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(anymultirange_type))
+					anymultirange_type = get_call_expr_argtype(call_expr, i);
+				break;
 			default:
 				break;
 		}
@@ -540,7 +549,7 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 
 	/* If nothing found, parser messed up */
 	if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type) &&
-		!OidIsValid(anyrange_type))
+		!OidIsValid(anyrange_type) && !OidIsValid(anymultirange_type))
 		return false;
 
 	/* If needed, deduce one polymorphic type from others */
@@ -561,19 +570,122 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				return false;
 			anyelement_type = subtype;
 		}
+
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
+													   anymultirange_type,
+													   ANYMULTIRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
+				return false;
+			anyrange_type = rngtype;
+
+			Oid			subtype = resolve_generic_type(ANYELEMENTOID,
+													   anyrange_type,
+													   ANYRANGEOID);
+
+			/* check for inconsistent array and multirange results */
+			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
+				return false;
+			anyelement_type = subtype;
+		}
 	}
 
 	if (have_anyarray_result && !OidIsValid(anyarray_type))
-		anyarray_type = resolve_generic_type(ANYARRAYOID,
-											 anyelement_type,
-											 ANYELEMENTOID);
+	{
+		if (OidIsValid(anyelement_type))
+		{
+			anyarray_type = resolve_generic_type(ANYARRAYOID,
+												 anyelement_type,
+												 ANYELEMENTOID);
+		}
+
+		/* An array type should have the same elemtype as the range type */
+		if (!OidIsValid(anyarray_type) && OidIsValid(anyrange_type))
+		{
+			anyarray_type = resolve_generic_type(ANYARRAYOID,
+												 anyrange_type,
+												 ANYRANGEOID);
+		}
+
+		/* An array type should have the same elemtype as the multirange type */
+		if (!OidIsValid(anyarray_type) && OidIsValid(anymultirange_type))
+		{
+			anyarray_type = resolve_generic_type(ANYARRAYOID,
+												 anymultirange_type,
+												 ANYMULTIRANGEOID);
+		}
+	}
 
 	/*
 	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types for the same subtype.
+	 * there may be multiple range types for the same subtype, but a
+	 * multirange might help.
 	 */
 	if (have_anyrange_result && !OidIsValid(anyrange_type))
-		return false;
+	{
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
+													   anymultirange_type,
+													   ANYMULTIRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
+				return false;
+			anyrange_type = rngtype;
+
+			Oid			subtype = resolve_generic_type(ANYELEMENTOID,
+													   anyrange_type,
+													   ANYRANGEOID);
+
+			/* check for inconsistent array and multirange results */
+			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
+				return false;
+			anyelement_type = subtype;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/*
+	 * Likewise we can't deduce a multirange from most polymorphic inputs, but
+	 * a range type would work.
+	 */
+	if (have_anymultirange_result && !OidIsValid(anymultirange_type))
+	{
+		if (OidIsValid(anyrange_type))
+		{
+			Oid			subtype = resolve_generic_type(ANYELEMENTOID,
+													   anyrange_type,
+													   ANYRANGEOID);
+
+			/* check for inconsistent array and range results */
+			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
+				return false;
+			anyelement_type = subtype;
+
+			Oid			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+														  anyrange_type,
+														  ANYRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			Oid			rngtype = get_multirange_subtype(mltrngtype);
+
+			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
+				return false;
+			anyrange_type = rngtype;
+			anymultirange_type = mltrngtype;
+		}
+		else
+		{
+			return false;
+		}
+	}
 
 	/* Enforce ANYNONARRAY if needed */
 	if (have_anynonarray && type_is_array(anyelement_type))
@@ -640,6 +752,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 								   0);
 				/* no collation should be attached to a range type */
 				break;
+			case ANYMULTIRANGEOID:
+				TupleDescInitEntry(tupdesc, i + 1,
+								   NameStr(att->attname),
+								   anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			default:
 				break;
 		}
@@ -664,9 +784,11 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	bool		have_anyelement_result = false;
 	bool		have_anyarray_result = false;
 	bool		have_anyrange_result = false;
+	bool		have_anymultirange_result = false;
 	Oid			anyelement_type = InvalidOid;
 	Oid			anyarray_type = InvalidOid;
 	Oid			anyrange_type = InvalidOid;
+	Oid			anymultirange_type = InvalidOid;
 	int			inargno;
 	int			i;
 
@@ -725,6 +847,21 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+					have_anymultirange_result = true;
+				else
+				{
+					if (!OidIsValid(anymultirange_type))
+					{
+						anymultirange_type = get_call_expr_argtype(call_expr,
+																   inargno);
+						if (!OidIsValid(anymultirange_type))
+							return false;
+					}
+					argtypes[i] = anymultirange_type;
+				}
+				break;
 			default:
 				break;
 		}
@@ -734,12 +871,12 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 
 	/* Done? */
 	if (!have_anyelement_result && !have_anyarray_result &&
-		!have_anyrange_result)
+		!have_anyrange_result && !have_anymultirange_result)
 		return true;
 
 	/* If no input polymorphics, parser messed up */
 	if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type) &&
-		!OidIsValid(anyrange_type))
+		!OidIsValid(anyrange_type) && !OidIsValid(anymultirange_type))
 		return false;
 
 	/* If needed, deduce one polymorphic type from others */
@@ -760,19 +897,122 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 				return false;
 			anyelement_type = subtype;
 		}
+
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
+													   anymultirange_type,
+													   ANYMULTIRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
+				return false;
+			anyrange_type = rngtype;
+
+			Oid			subtype = resolve_generic_type(ANYELEMENTOID,
+													   anyrange_type,
+													   ANYRANGEOID);
+
+			/* check for inconsistent array and multirange results */
+			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
+				return false;
+			anyelement_type = subtype;
+		}
 	}
 
 	if (have_anyarray_result && !OidIsValid(anyarray_type))
-		anyarray_type = resolve_generic_type(ANYARRAYOID,
-											 anyelement_type,
-											 ANYELEMENTOID);
+	{
+		if (OidIsValid(anyelement_type))
+		{
+			anyarray_type = resolve_generic_type(ANYARRAYOID,
+												 anyelement_type,
+												 ANYELEMENTOID);
+		}
+
+		/* An array type should have the same elemtype as the range type */
+		if (!OidIsValid(anyarray_type) && OidIsValid(anyrange_type))
+		{
+			anyarray_type = resolve_generic_type(ANYARRAYOID,
+												 anyrange_type,
+												 ANYRANGEOID);
+		}
+
+		/* An array type should have the same elemtype as the multirange type */
+		if (!OidIsValid(anyarray_type) && OidIsValid(anymultirange_type))
+		{
+			anyarray_type = resolve_generic_type(ANYARRAYOID,
+												 anymultirange_type,
+												 ANYMULTIRANGEOID);
+		}
+	}
 
 	/*
 	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types for the same subtype.
+	 * there may be multiple range types for the same subtype, but a
+	 * multirange might help.
 	 */
 	if (have_anyrange_result && !OidIsValid(anyrange_type))
-		return false;
+	{
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
+													   anymultirange_type,
+													   ANYMULTIRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
+				return false;
+			anyrange_type = rngtype;
+
+			Oid			subtype = resolve_generic_type(ANYELEMENTOID,
+													   anyrange_type,
+													   ANYRANGEOID);
+
+			/* check for inconsistent array and multirange results */
+			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
+				return false;
+			anyelement_type = subtype;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/*
+	 * Likewise we can't deduce a multirange from most polymorphic inputs, but
+	 * a range type would work.
+	 */
+	if (have_anymultirange_result && !OidIsValid(anymultirange_type))
+	{
+		if (OidIsValid(anyrange_type))
+		{
+			Oid			subtype = resolve_generic_type(ANYELEMENTOID,
+													   anyrange_type,
+													   ANYRANGEOID);
+
+			/* check for inconsistent array and range results */
+			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
+				return false;
+			anyelement_type = subtype;
+
+			Oid			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+														  anyrange_type,
+														  ANYRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			Oid			rngtype = get_multirange_subtype(mltrngtype);
+
+			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
+				return false;
+			anyrange_type = rngtype;
+
+		}
+		else
+		{
+			return false;
+		}
+	}
 
 	/* XXX do we need to enforce ANYNONARRAY or ANYENUM here?  I think not */
 
@@ -792,6 +1032,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = anymultirange_type;
+				break;
 			default:
 				break;
 		}
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 23cf481e78..50b4c4428f 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -137,8 +137,8 @@
  *	* we need to estimate alignment padding cost abstractly, ie without
  *	  reference to a real tuple.  We must assume the worst case that
  *	  all varlenas are aligned.
- *	* within arrays, we unconditionally align varlenas (XXX this should be
- *	  revisited, probably).
+ *	* within arrays and multiranges, we unconditionally align varlenas (XXX this
+ *	  should be revisited, probably).
  *
  * The attalign cases are tested in what is hopefully something like their
  * frequency of occurrence.
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ef4445b017..ca9890ab6c 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -330,6 +330,9 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_range_mltrngtypid_index, 8001, on pg_range using btree(mltrngtypid oid_ops));
+#define RangeMultirangeTypidIndexId			8001
+
 DECLARE_UNIQUE_INDEX(pg_policy_oid_index, 3257, on pg_policy using btree(oid oid_ops));
 #define PolicyOidIndexId				3257
 
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 232557ee81..999493f94d 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1356,6 +1356,28 @@
   amoprighttype => 'anyrange', amopstrategy => '18',
   amopopr => '=(anyrange,anyrange)', amopmethod => 'gist' },
 
+# btree multirange_ops
+{ amopfamily => 'btree/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '1',
+  amopopr => '<(anymultirange,anymultirange)', amopmethod => 'btree' },
+{ amopfamily => 'btree/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '2',
+  amopopr => '<=(anymultirange,anymultirange)', amopmethod => 'btree' },
+{ amopfamily => 'btree/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '3',
+  amopopr => '=(anymultirange,anymultirange)', amopmethod => 'btree' },
+{ amopfamily => 'btree/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '4',
+  amopopr => '>=(anymultirange,anymultirange)', amopmethod => 'btree' },
+{ amopfamily => 'btree/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '5',
+  amopopr => '>(anymultirange,anymultirange)', amopmethod => 'btree' },
+
+# hash multirange_ops
+{ amopfamily => 'hash/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '1',
+  amopopr => '=(anymultirange,anymultirange)', amopmethod => 'hash' },
+
 # SP-GiST quad_point_ops
 { amopfamily => 'spgist/quad_point_ops', amoplefttype => 'point',
   amoprighttype => 'point', amopstrategy => '11', amopopr => '>^(point,point)',
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 5e705019b4..b7cb2cb000 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -225,6 +225,8 @@
   amprocrighttype => 'tsquery', amprocnum => '1', amproc => 'tsquery_cmp' },
 { amprocfamily => 'btree/range_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '1', amproc => 'range_cmp' },
+{ amprocfamily => 'btree/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '1', amproc => 'multirange_cmp' },
 { amprocfamily => 'btree/jsonb_ops', amproclefttype => 'jsonb',
   amprocrighttype => 'jsonb', amprocnum => '1', amproc => 'jsonb_cmp' },
 
@@ -385,6 +387,11 @@
 { amprocfamily => 'hash/range_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '2',
   amproc => 'hash_range_extended' },
+{ amprocfamily => 'hash/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '1', amproc => 'hash_multirange' },
+{ amprocfamily => 'hash/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '2',
+  amproc => 'hash_multirange_extended' },
 { amprocfamily => 'hash/jsonb_ops', amproclefttype => 'jsonb',
   amprocrighttype => 'jsonb', amprocnum => '1', amproc => 'jsonb_hash' },
 { amprocfamily => 'hash/jsonb_ops', amproclefttype => 'jsonb',
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index 2d575102ef..2e59cf9159 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -226,6 +226,10 @@
   opcintype => 'anyrange' },
 { opcmethod => 'spgist', opcname => 'range_ops',
   opcfamily => 'spgist/range_ops', opcintype => 'anyrange' },
+{ opcmethod => 'btree', opcname => 'multirange_ops', opcfamily => 'btree/multirange_ops',
+  opcintype => 'anymultirange' },
+{ opcmethod => 'hash', opcname => 'multirange_ops', opcfamily => 'hash/multirange_ops',
+  opcintype => 'anymultirange' },
 { opcmethod => 'spgist', opcname => 'box_ops', opcfamily => 'spgist/box_ops',
   opcintype => 'box' },
 { opcmethod => 'spgist', opcname => 'quad_point_ops',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 126bfac3ff..7b7ce38076 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3297,5 +3297,38 @@
   oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'contsel', oprjoin => 'contjoinsel' },
+{ oid => '8027', descr => 'equal',
+  oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'anymultirange',
+  oprright => 'anymultirange', oprresult => 'bool', oprcom => '=(anymultirange,anymultirange)',
+  oprnegate => '<>(anymultirange,anymultirange)', oprcode => 'multirange_eq',
+  oprrest => 'eqsel', oprjoin => 'eqjoinsel' },
+{ oid => '8028', descr => 'not equal',
+  oprname => '<>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<>(anymultirange,anymultirange)',
+  oprnegate => '=(anymultirange,anymultirange)', oprcode => 'multirange_ne',
+  oprrest => 'neqsel', oprjoin => 'neqjoinsel' },
+{ oid => '8029', oid_symbol => 'OID_MULTIRANGE_LESS_OP', descr => 'less than',
+  oprname => '<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>(anymultirange,anymultirange)',
+  oprnegate => '>=(anymultirange,anymultirange)', oprcode => 'multirange_lt',
+  oprrest => 'scalarltsel', oprjoin => 'scalarltjoinsel' },
+{ oid => '8030', oid_symbol => 'OID_MULTIRANGE_LESS_EQUAL_OP',
+  descr => 'less than or equal',
+  oprname => '<=', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>=(anymultirange,anymultirange)',
+  oprnegate => '>(anymultirange,anymultirange)', oprcode => 'multirange_le',
+  oprrest => 'scalarlesel', oprjoin => 'scalarlejoinsel' },
+{ oid => '8031', oid_symbol => 'OID_MULTIRANGE_GREATER_EQUAL_OP',
+  descr => 'greater than or equal',
+  oprname => '>=', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<=(anymultirange,anymultirange)',
+  oprnegate => '<(anymultirange,anymultirange)', oprcode => 'multirange_ge',
+  oprrest => 'scalargesel', oprjoin => 'scalargejoinsel' },
+{ oid => '8032', oid_symbol => 'OID_MULTIRANGE_GREATER_OP',
+  descr => 'greater than',
+  oprname => '>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<(anymultirange,anymultirange)',
+  oprnegate => '<=(anymultirange,anymultirange)', oprcode => 'multirange_gt',
+  oprrest => 'scalargtsel', oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 41e40d657a..536c10cb0c 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -226,5 +226,9 @@
   opfmethod => 'spgist', opfname => 'box_ops' },
 { oid => '5008',
   opfmethod => 'spgist', opfname => 'poly_ops' },
+{ oid => '8021',
+  opfmethod => 'btree', opfname => 'multirange_ops' },
+{ oid => '8035',
+  opfmethod => 'hash', opfname => 'multirange_ops' },
 
 ]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ac8f64b219..abb956514e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9722,6 +9722,83 @@
   proname => 'int8range', proisstrict => 'f', prorettype => 'int8range',
   proargtypes => 'int8 int8 text', prosrc => 'range_constructor3' },
 
+# functions for multiranges
+{ oid => '8002', descr => 'I/O',
+  proname => 'anymultirange_in', provolatile => 's', prorettype => 'anymultirange',
+  proargtypes => 'cstring oid int4', prosrc => 'anymultirange_in' },
+{ oid => '8003', descr => 'I/O',
+  proname => 'anymultirange_out', provolatile => 's', prorettype => 'cstring',
+  proargtypes => 'anymultirange', prosrc => 'anymultirange_out' },
+{ oid => '8004', descr => 'I/O',
+  proname => 'multirange_in', provolatile => 's', prorettype => 'anymultirange',
+  proargtypes => 'cstring oid int4', prosrc => 'multirange_in' },
+{ oid => '8005', descr => 'I/O',
+  proname => 'multirange_out', provolatile => 's', prorettype => 'cstring',
+  proargtypes => 'anymultirange', prosrc => 'multirange_out' },
+{ oid => '8006', descr => 'I/O',
+  proname => 'multirange_recv', provolatile => 's', prorettype => 'anymultirange',
+  proargtypes => 'internal oid int4', prosrc => 'multirange_recv' },
+{ oid => '8007', descr => 'I/O',
+  proname => 'multirange_send', provolatile => 's', prorettype => 'bytea',
+  proargtypes => 'anymultirange', prosrc => 'multirange_send' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor1' },
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index dd9baa2678..9fd781e7b5 100644
--- a/src/include/catalog/pg_range.dat
+++ b/src/include/catalog/pg_range.dat
@@ -13,19 +13,22 @@
 [
 
 { rngtypid => 'int4range', rngsubtype => 'int4', rngsubopc => 'btree/int4_ops',
-  rngcanonical => 'int4range_canonical', rngsubdiff => 'int4range_subdiff' },
+  rngcanonical => 'int4range_canonical', rngsubdiff => 'int4range_subdiff',
+  mltrngtypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', mltrngtypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', mltrngtypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', mltrngtypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  mltrngtypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  mltrngtypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index b01a074d09..9cfc56d4d2 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -45,6 +45,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 
 	/* subtype difference as a float8, or 0 */
 	regproc		rngsubdiff BKI_LOOKUP(pg_proc);
+
+	/* OID of the range's multirange type */
+	Oid			mltrngtypid BKI_LOOKUP(pg_type);
 } FormData_pg_range;
 
 /* ----------------
@@ -60,7 +63,7 @@ typedef FormData_pg_range *Form_pg_range;
 
 extern void RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 						Oid rangeSubOpclass, RegProcedure rangeCanonical,
-						RegProcedure rangeSubDiff);
+						RegProcedure rangeSubDiff, Oid multirangeTypeOid);
 extern void RangeDelete(Oid rangeTypeOid);
 
 #endif							/* PG_RANGE_H */
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index d9b35af914..9a84d029d1 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -486,6 +486,40 @@
   typreceive => 'range_recv', typsend => 'range_send',
   typanalyze => 'range_typanalyze', typalign => 'd', typstorage => 'x' },
 
+# multirange types
+{ oid => '8009', array_type_oid => '8010', descr => 'multirange of integers',
+  typname => 'int4multirange', typlen => '-1', typbyval => 'f', typtype => 'm',
+  typcategory => 'M', typinput => 'multirange_in', typoutput => 'multirange_out',
+  typreceive => 'multirange_recv', typsend => 'multirange_send',
+  typanalyze => 'multirange_typanalyze', typalign => 'i', typstorage => 'x' },
+{ oid => '8011', array_type_oid => '8012', descr => 'multirange of numerics',
+  typname => 'nummultirange', typlen => '-1', typbyval => 'f', typtype => 'm',
+  typcategory => 'M', typinput => 'multirange_in', typoutput => 'multirange_out',
+  typreceive => 'multirange_recv', typsend => 'multirange_send',
+  typanalyze => 'multirange_typanalyze', typalign => 'i', typstorage => 'x' },
+{ oid => '8013', array_type_oid => '8014',
+  descr => 'multirange of timestamps without time zone',
+  typname => 'tsmultirange', typlen => '-1', typbyval => 'f', typtype => 'm',
+  typcategory => 'M', typinput => 'multirange_in', typoutput => 'multirange_out',
+  typreceive => 'multirange_recv', typsend => 'multirange_send',
+  typanalyze => 'multirange_typanalyze', typalign => 'd', typstorage => 'x' },
+{ oid => '8015', array_type_oid => '8016',
+  descr => 'multirange of timestamps with time zone',
+  typname => 'tstzmultirange', typlen => '-1', typbyval => 'f', typtype => 'm',
+  typcategory => 'M', typinput => 'multirange_in', typoutput => 'multirange_out',
+  typreceive => 'multirange_recv', typsend => 'multirange_send',
+  typanalyze => 'multirange_typanalyze', typalign => 'd', typstorage => 'x' },
+{ oid => '8017', array_type_oid => '8018', descr => 'multirange of dates',
+  typname => 'datemultirange', typlen => '-1', typbyval => 'f', typtype => 'm',
+  typcategory => 'M', typinput => 'multirange_in', typoutput => 'multirange_out',
+  typreceive => 'multirange_recv', typsend => 'multirange_send',
+  typanalyze => 'multirange_typanalyze', typalign => 'i', typstorage => 'x' },
+{ oid => '8019', array_type_oid => '8020', descr => 'multirange of bigints',
+  typname => 'int8multirange', typlen => '-1', typbyval => 'f', typtype => 'm',
+  typcategory => 'M', typinput => 'multirange_in', typoutput => 'multirange_out',
+  typreceive => 'multirange_recv', typsend => 'multirange_send',
+  typanalyze => 'multirange_typanalyze', typalign => 'd', typstorage => 'x' },
+
 # pseudo-types
 # types with typtype='p' represent various special cases in the type system.
 # These cannot be used to define table columns, but are valid as function
@@ -594,5 +628,10 @@
   typname => 'anyrange', typlen => '-1', typbyval => 'f', typtype => 'p',
   typcategory => 'P', typinput => 'anyrange_in', typoutput => 'anyrange_out',
   typreceive => '-', typsend => '-', typalign => 'd', typstorage => 'x' },
+{ oid => '8000',
+  descr => 'pseudo-type representing a polymorphic base type that is a multirange',
+  typname => 'anymultirange', typlen => '-1', typbyval => 'f', typtype => 'p',
+  typcategory => 'P', typinput => 'anymultirange_in', typoutput => 'anymultirange_out',
+  typreceive => '-', typsend => '-', typalign => 'd', typstorage => 'x' },
 
 ]
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 0c273a0449..4f71a7f451 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -258,6 +258,7 @@ typedef FormData_pg_type *Form_pg_type;
 #define  TYPTYPE_COMPOSITE	'c' /* composite (e.g., table's rowtype) */
 #define  TYPTYPE_DOMAIN		'd' /* domain over another type */
 #define  TYPTYPE_ENUM		'e' /* enumerated type */
+#define  TYPTYPE_MULTIRANGE	'm' /* multirange type */
 #define  TYPTYPE_PSEUDO		'p' /* pseudo-type */
 #define  TYPTYPE_RANGE		'r' /* range type */
 
@@ -269,6 +270,7 @@ typedef FormData_pg_type *Form_pg_type;
 #define  TYPCATEGORY_ENUM		'E'
 #define  TYPCATEGORY_GEOMETRIC	'G'
 #define  TYPCATEGORY_NETWORK	'I' /* think INET */
+#define  TYPCATEGORY_MULTIRANGE	'M'
 #define  TYPCATEGORY_NUMERIC	'N'
 #define  TYPCATEGORY_PSEUDOTYPE 'P'
 #define  TYPCATEGORY_RANGE		'R'
@@ -284,7 +286,8 @@ typedef FormData_pg_type *Form_pg_type;
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
@@ -343,4 +346,7 @@ extern char *makeArrayTypeName(const char *typeName, Oid typeNamespace);
 extern bool moveArrayTypeName(Oid typeOid, const char *typeName,
 							  Oid typeNamespace);
 
+extern char *makeMultirangeTypeName(const char *rangeTypeName,
+									Oid typeNamespace);
+
 #endif							/* PG_TYPE_H */
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index c8df5bff9f..ffdd90d69c 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -154,6 +154,7 @@ extern char get_typtype(Oid typid);
 extern bool type_is_rowtype(Oid typid);
 extern bool type_is_enum(Oid typid);
 extern bool type_is_range(Oid typid);
+extern bool type_is_multirange(Oid typid);
 extern void get_type_category_preferred(Oid typid,
 										char *typcategory,
 										bool *typispreferred);
@@ -179,6 +180,8 @@ extern void free_attstatsslot(AttStatsSlot *sslot);
 extern char *get_namespace_name(Oid nspid);
 extern char *get_namespace_name_or_temp(Oid nspid);
 extern Oid	get_range_subtype(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_multirange_subtype(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 0000000000..ed2e19aafa
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,72 @@
+/*-------------------------------------------------------------------------
+ *
+ * multirangetypes.h
+ *	  Declarations for Postgres multirange types.
+ *
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/multirangetypes.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef MULTIRANGETYPES_H
+#define MULTIRANGETYPES_H
+
+#include "utils/typcache.h"
+
+
+/*
+ * Multiranges are varlena objects, so must meet the varlena convention that
+ * the first int32 of the object contains the total object size in bytes.
+ * Be sure to use VARSIZE() and SET_VARSIZE() to access it, though!
+ */
+typedef struct
+{
+	char		vl_len_[4];		/* varlena header (do not touch directly!) */
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;		/* the number of ranges */
+
+	/*
+	 * Following the OID are the range objects themselves. Note that ranges
+	 * are varlena too, depending on whether they have lower/upper bounds and
+	 * because even their base types can be varlena. So we can't really index
+	 * into this list.
+	 */
+}			MultirangeType;
+
+/* Use this macro in preference to fetching multirangetypid field directly */
+#define MultirangeTypeGetOid(mr)	((mr)->multirangetypid)
+#define MultirangeIsEmpty(mr)  ((mr)->rangeCount == 0)
+
+/*
+ * fmgr macros for multirange type objects
+ */
+#define DatumGetMultirangeTypeP(X)		((MultirangeType *) PG_DETOAST_DATUM(X))
+#define DatumGetMultirangeTypePCopy(X)	((MultirangeType *) PG_DETOAST_DATUM_COPY(X))
+#define MultirangeTypePGetDatum(X)		PointerGetDatum(X)
+#define PG_GETARG_MULTIRANGE_P(n)		DatumGetMultirangeTypeP(PG_GETARG_DATUM(n))
+#define PG_GETARG_MULTIRANGE_P_COPY(n)	DatumGetMultirangeTypePCopy(PG_GETARG_DATUM(n))
+#define PG_RETURN_MULTIRANGE_P(x)		return MultirangeTypePGetDatum(x)
+
+/*
+ * prototypes for functions defined in multirangetypes.c
+ */
+
+/* internal versions of the above */
+extern bool multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+								   MultirangeType * mr2);
+extern bool multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+								   MultirangeType * mr2);
+
+/* assorted support functions */
+extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
+											   Oid mltrngtypid);
+extern void multirange_deserialize(MultirangeType * range,
+								   int32 *range_count, RangeType ***ranges);
+extern MultirangeType * make_multirange(Oid mltrngtypoid,
+										TypeCacheEntry *typcache, int32 range_count, RangeType **ranges);
+extern MultirangeType * make_empty_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp);
+
+#endif							/* MULTIRANGETYPES_H */
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index 11ed19ccfd..521710d71f 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -29,6 +29,8 @@ typedef struct
 	/* Following the OID are zero to two bound values, then a flags byte */
 } RangeType;
 
+#define RANGE_EMPTY_LITERAL "empty"
+
 /* Use this macro in preference to fetching rangetypid field directly */
 #define RangeTypeGetOid(r)	((r)->rangetypid)
 
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 918765cc99..32603eeb58 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -66,6 +66,7 @@ enum SysCacheIdentifier
 	INDEXRELID,
 	LANGNAME,
 	LANGOID,
+	MULTIRANGETYPE,
 	NAMESPACENAME,
 	NAMESPACEOID,
 	OPERNAMENSP,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 04bf28180d..eda39881ef 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -98,6 +98,11 @@ typedef struct TypeCacheEntry
 	FmgrInfo	rng_canonical_finfo;	/* canonicalization function, if any */
 	FmgrInfo	rng_subdiff_finfo;	/* difference function, if any */
 
+	/*
+	 * Fields computed when TYPCACHE_MULTIRANGE_INFO is required.
+	 */
+	struct TypeCacheEntry *rngtype;
+
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
@@ -141,6 +146,7 @@ typedef struct TypeCacheEntry
 #define TYPECACHE_DOMAIN_CONSTR_INFO		0x2000
 #define TYPECACHE_HASH_EXTENDED_PROC		0x4000
 #define TYPECACHE_HASH_EXTENDED_PROC_FINFO	0x8000
+#define TYPECACHE_MULTIRANGE_INFO			0x10000
 
 /* This value will not equal any valid tupledesc identifier, nor 0 */
 #define INVALID_TUPLEDESC_IDENTIFIER ((uint64) 1)
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 3fb8fc7bad..f575f882a2 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -511,6 +511,8 @@ do_compile(FunctionCallInfo fcinfo,
 						rettypeid = INT4ARRAYOID;
 					else if (rettypeid == ANYRANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2494,6 +2496,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYRANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/hash_func.out b/src/test/regress/expected/hash_func.out
index da0948e95a..b446bfcbb7 100644
--- a/src/test/regress/expected/hash_func.out
+++ b/src/test/regress/expected/hash_func.out
@@ -298,3 +298,16 @@ WHERE  hash_range(v)::bit(32) != hash_range_extended(v, 0)::bit(32)
 -------+----------+-----------+-----------
 (0 rows)
 
+SELECT v as value, hash_multirange(v)::bit(32) as standard,
+	   hash_multirange_extended(v, 0)::bit(32) as extended0,
+	   hash_multirange_extended(v, 1)::bit(32) as extended1
+FROM   (VALUES ('{[10,20)}'::int4multirange), ('{[23, 43]}'::int4multirange),
+         ('{[5675, 550273)}'::int4multirange),
+		 ('{[550274, 1550274)}'::int4multirange),
+		 ('{[1550275, 208112489)}'::int4multirange)) x(v)
+WHERE  hash_multirange(v)::bit(32) != hash_multirange_extended(v, 0)::bit(32)
+       OR hash_multirange(v)::bit(32) = hash_multirange_extended(v, 1)::bit(32);
+ value | standard | extended0 | extended1 
+-------+----------+-----------+-----------
+(0 rows)
+
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
new file mode 100644
index 0000000000..5749537635
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,292 @@
+-- Tests for multirange data types.
+--
+-- test input parser
+--
+-- negative tests; should fail
+select ''::textmultirange;
+ERROR:  malformed multirange literal: ""
+LINE 1: select ''::textmultirange;
+               ^
+DETAIL:  Missing left bracket.
+select '{,}'::textmultirange;
+ERROR:  malformed multirange literal: "{,}"
+LINE 1: select '{,}'::textmultirange;
+               ^
+DETAIL:  Expected range start.
+select '{[a,c),}'::textmultirange;
+ERROR:  malformed multirange literal: "{[a,c),}"
+LINE 1: select '{[a,c),}'::textmultirange;
+               ^
+DETAIL:  Expected range start.
+select '{,[a,c)}'::textmultirange;
+ERROR:  malformed multirange literal: "{,[a,c)}"
+LINE 1: select '{,[a,c)}'::textmultirange;
+               ^
+DETAIL:  Expected range start.
+select '{-[a,z)}'::textmultirange;
+ERROR:  malformed multirange literal: "{-[a,z)}"
+LINE 1: select '{-[a,z)}'::textmultirange;
+               ^
+DETAIL:  Expected range start.
+select '{[a,z) - }'::textmultirange;
+ERROR:  malformed multirange literal: "{[a,z) - }"
+LINE 1: select '{[a,z) - }'::textmultirange;
+               ^
+DETAIL:  Expected comma or end of multirange.
+select '{(",a)}'::textmultirange;
+ERROR:  malformed multirange literal: "{(",a)}"
+LINE 1: select '{(",a)}'::textmultirange;
+               ^
+DETAIL:  Unexpected end of input.
+select '{(,,a)}'::textmultirange;
+ERROR:  malformed range literal: "(,,a)"
+LINE 1: select '{(,,a)}'::textmultirange;
+               ^
+DETAIL:  Too many commas.
+select '{(),a)}'::textmultirange;
+ERROR:  malformed range literal: "()"
+LINE 1: select '{(),a)}'::textmultirange;
+               ^
+DETAIL:  Missing comma after lower bound.
+select '{(a,))}'::textmultirange;
+ERROR:  malformed multirange literal: "{(a,))}"
+LINE 1: select '{(a,))}'::textmultirange;
+               ^
+DETAIL:  Expected comma or end of multirange.
+select '{(],a)}'::textmultirange;
+ERROR:  malformed range literal: "(]"
+LINE 1: select '{(],a)}'::textmultirange;
+               ^
+DETAIL:  Missing comma after lower bound.
+select '{(a,])}'::textmultirange;
+ERROR:  malformed multirange literal: "{(a,])}"
+LINE 1: select '{(a,])}'::textmultirange;
+               ^
+DETAIL:  Expected comma or end of multirange.
+select '{[z,a]}'::textmultirange;
+ERROR:  range lower bound must be less than or equal to range upper bound
+LINE 1: select '{[z,a]}'::textmultirange;
+               ^
+-- should succeed
+select '{}'::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select '  {}  '::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select ' { empty, empty }  '::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select ' {( " a " " a ", " z " " z " )  }'::textmultirange;
+       textmultirange       
+----------------------------
+ {("  a   a ","  z   z  ")}
+(1 row)
+
+select '{(,z)}'::textmultirange;
+ textmultirange 
+----------------
+ {(,z)}
+(1 row)
+
+select '{(a,)}'::textmultirange;
+ textmultirange 
+----------------
+ {(a,)}
+(1 row)
+
+select '{[,z]}'::textmultirange;
+ textmultirange 
+----------------
+ {(,z]}
+(1 row)
+
+select '{[a,]}'::textmultirange;
+ textmultirange 
+----------------
+ {[a,)}
+(1 row)
+
+select '{(,)}'::textmultirange;
+ textmultirange 
+----------------
+ {(,)}
+(1 row)
+
+select '{[ , ]}'::textmultirange;
+ textmultirange 
+----------------
+ {[" "," "]}
+(1 row)
+
+select '{["",""]}'::textmultirange;
+ textmultirange 
+----------------
+ {["",""]}
+(1 row)
+
+select '{[",",","]}'::textmultirange;
+ textmultirange 
+----------------
+ {[",",","]}
+(1 row)
+
+select '{["\\","\\"]}'::textmultirange;
+ textmultirange 
+----------------
+ {["\\","\\"]}
+(1 row)
+
+select '{["""","\""]}'::textmultirange;
+ textmultirange 
+----------------
+ {["""",""""]}
+(1 row)
+
+select '{(\\,a)}'::textmultirange;
+ textmultirange 
+----------------
+ {("\\",a)}
+(1 row)
+
+select '{((,z)}'::textmultirange;
+ textmultirange 
+----------------
+ {("(",z)}
+(1 row)
+
+select '{([,z)}'::textmultirange;
+ textmultirange 
+----------------
+ {("[",z)}
+(1 row)
+
+select '{(!,()}'::textmultirange;
+ textmultirange 
+----------------
+ {(!,"(")}
+(1 row)
+
+select '{(!,[)}'::textmultirange;
+ textmultirange 
+----------------
+ {(!,"[")}
+(1 row)
+
+select '{[a,a]}'::textmultirange;
+ textmultirange 
+----------------
+ {[a,a]}
+(1 row)
+
+select '{[a,a],[a,b]}'::textmultirange;
+ textmultirange 
+----------------
+ {[a,b]}
+(1 row)
+
+select '{[a,b), [b,e]}'::textmultirange;
+ textmultirange 
+----------------
+ {[a,e]}
+(1 row)
+
+select '{[a,d), [b,f]}'::textmultirange;
+ textmultirange 
+----------------
+ {[a,f]}
+(1 row)
+
+select '{[a,a],[b,b]}'::textmultirange;
+ textmultirange 
+----------------
+ {[a,a],[b,b]}
+(1 row)
+
+-- without canonicalization, we can't join these:
+select '{[a,a], [b,b]}'::textmultirange;
+ textmultirange 
+----------------
+ {[a,a],[b,b]}
+(1 row)
+
+-- with canonicalization, we can join these:
+select '{[1,2], [3,4]}'::int4multirange;
+ int4multirange 
+----------------
+ {[1,5)}
+(1 row)
+
+select '{[a,a], [b,b], [c,c]}'::textmultirange;
+   textmultirange    
+---------------------
+ {[a,a],[b,b],[c,c]}
+(1 row)
+
+select '{[a,d], [b,e]}'::textmultirange;
+ textmultirange 
+----------------
+ {[a,e]}
+(1 row)
+
+select '{[a,d), [d,e)}'::textmultirange;
+ textmultirange 
+----------------
+ {[a,e)}
+(1 row)
+
+-- these are allowed but normalize to empty:
+select '{[a,a)}'::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select '{(a,a]}'::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select '{(a,a)}'::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+--
+-- test the constructor
+---
+select textmultirange();
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textmultirange(textrange('a', 'c'));
+ textmultirange 
+----------------
+ {[a,c)}
+(1 row)
+
+select textmultirange(textrange('a', 'c'), textrange('f', 'g'));
+ textmultirange 
+----------------
+ {[a,c),[f,g)}
+(1 row)
+
+select textmultirange(textrange('a', 'c'), textrange('b', 'd'));
+ textmultirange 
+----------------
+ {[a,d)}
+(1 row)
+
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index c19740e5db..916e1016b5 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -38,6 +38,10 @@ begin
     if (select typtype from pg_catalog.pg_type where oid = $1) = 'r'
     then return true; end if;
   end if;
+  if $2 = 'pg_catalog.anymultirange'::pg_catalog.regtype then
+    if (select typtype from pg_catalog.pg_type where oid = $1) = 'm'
+    then return true; end if;
+  end if;
   return false;
 end
 $$ language plpgsql strict stable;
@@ -181,7 +185,8 @@ WHERE p1.oid < p2.oid AND
 -- need to be modified whenever new pairs of types are made binary-equivalent,
 -- or when new polymorphic built-in functions are added!
 -- Note: ignore aggregate functions here, since they all point to the same
--- dummy built-in function.  Likewise, ignore range constructor functions.
+-- dummy built-in function.  Likewise, ignore range and multirange constructor
+-- functions.
 SELECT DISTINCT p1.prorettype, p2.prorettype
 FROM pg_proc AS p1, pg_proc AS p2
 WHERE p1.oid != p2.oid AND
@@ -190,6 +195,8 @@ WHERE p1.oid != p2.oid AND
     p1.prokind != 'a' AND p2.prokind != 'a' AND
     p1.prosrc NOT LIKE E'range\\_constructor_' AND
     p2.prosrc NOT LIKE E'range\\_constructor_' AND
+    p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
+    p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
     (p1.prorettype < p2.prorettype)
 ORDER BY 1, 2;
  prorettype | prorettype 
@@ -206,6 +213,8 @@ WHERE p1.oid != p2.oid AND
     p1.prokind != 'a' AND p2.prokind != 'a' AND
     p1.prosrc NOT LIKE E'range\\_constructor_' AND
     p2.prosrc NOT LIKE E'range\\_constructor_' AND
+    p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
+    p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
     (p1.proargtypes[0] < p2.proargtypes[0])
 ORDER BY 1, 2;
  proargtypes | proargtypes 
@@ -224,6 +233,8 @@ WHERE p1.oid != p2.oid AND
     p1.prokind != 'a' AND p2.prokind != 'a' AND
     p1.prosrc NOT LIKE E'range\\_constructor_' AND
     p2.prosrc NOT LIKE E'range\\_constructor_' AND
+    p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
+    p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
     (p1.proargtypes[1] < p2.proargtypes[1])
 ORDER BY 1, 2;
  proargtypes | proargtypes 
@@ -329,13 +340,14 @@ SELECT p1.oid, p1.proname
 FROM pg_proc as p1
 WHERE p1.prorettype IN
     ('anyelement'::regtype, 'anyarray'::regtype, 'anynonarray'::regtype,
-     'anyenum'::regtype, 'anyrange'::regtype)
+     'anyenum'::regtype, 'anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
     ('anyelement'::regtype = ANY (p1.proargtypes) OR
      'anyarray'::regtype = ANY (p1.proargtypes) OR
      'anynonarray'::regtype = ANY (p1.proargtypes) OR
      'anyenum'::regtype = ANY (p1.proargtypes) OR
-     'anyrange'::regtype = ANY (p1.proargtypes))
+     'anyrange'::regtype = ANY (p1.proargtypes) OR
+     'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
@@ -343,16 +355,19 @@ ORDER BY 2;
  2502 | anyarray_recv
  2312 | anyelement_in
  3504 | anyenum_in
+ 8002 | anymultirange_in
  2777 | anynonarray_in
  3832 | anyrange_in
   750 | array_in
  2400 | array_recv
  3506 | enum_in
  3532 | enum_recv
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(13 rows)
+(16 rows)
 
 -- Look for functions that accept cstring and are neither datatype input
 -- functions nor encoding conversion functions.  It's almost never a good
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index cd7fc03b04..8ef9595c37 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -17,7 +17,7 @@ SELECT p1.oid, p1.typname
 FROM pg_type as p1
 WHERE p1.typnamespace = 0 OR
     (p1.typlen <= 0 AND p1.typlen != -1 AND p1.typlen != -2) OR
-    (p1.typtype not in ('b', 'c', 'd', 'e', 'p', 'r')) OR
+    (p1.typtype not in ('b', 'c', 'd', 'e', 'p', 'r', 'm')) OR
     NOT p1.typisdefined OR
     (p1.typalign not in ('c', 's', 'i', 'd')) OR
     (p1.typstorage not in ('p', 'x', 'e', 'm'));
@@ -193,18 +193,19 @@ WHERE p1.typinput = p2.oid AND p2.provolatile NOT IN ('i', 's');
 -----+---------+-----+---------
 (0 rows)
 
--- Composites, domains, enums, ranges should all use the same input routines
+-- Composites, domains, enums, multiranges, ranges should all use the same input routines
 SELECT DISTINCT typtype, typinput
 FROM pg_type AS p1
 WHERE p1.typtype not in ('b', 'p')
 ORDER BY 1;
- typtype | typinput  
----------+-----------
+ typtype |   typinput    
+---------+---------------
  c       | record_in
  d       | domain_in
  e       | enum_in
+ m       | multirange_in
  r       | range_in
-(4 rows)
+(5 rows)
 
 -- Check for bogus typoutput routines
 -- As of 8.0, this check finds refcursor, which is borrowing
@@ -238,17 +239,18 @@ WHERE p1.typoutput = p2.oid AND p2.provolatile NOT IN ('i', 's');
 -----+---------+-----+---------
 (0 rows)
 
--- Composites, enums, ranges should all use the same output routines
+-- Composites, enums, multiranges, ranges should all use the same output routines
 SELECT DISTINCT typtype, typoutput
 FROM pg_type AS p1
 WHERE p1.typtype not in ('b', 'd', 'p')
 ORDER BY 1;
- typtype | typoutput  
----------+------------
+ typtype |   typoutput    
+---------+----------------
  c       | record_out
  e       | enum_out
+ m       | multirange_out
  r       | range_out
-(3 rows)
+(4 rows)
 
 -- Domains should have same typoutput as their base types
 SELECT p1.oid, p1.typname, p2.oid, p2.typname
@@ -316,18 +318,19 @@ WHERE p1.typreceive = p2.oid AND p2.provolatile NOT IN ('i', 's');
 -----+---------+-----+---------
 (0 rows)
 
--- Composites, domains, enums, ranges should all use the same receive routines
+-- Composites, domains, enums, multiranges, ranges should all use the same receive routines
 SELECT DISTINCT typtype, typreceive
 FROM pg_type AS p1
 WHERE p1.typtype not in ('b', 'p')
 ORDER BY 1;
- typtype | typreceive  
----------+-------------
+ typtype |   typreceive    
+---------+-----------------
  c       | record_recv
  d       | domain_recv
  e       | enum_recv
+ m       | multirange_recv
  r       | range_recv
-(4 rows)
+(5 rows)
 
 -- Check for bogus typsend routines
 -- As of 7.4, this check finds refcursor, which is borrowing
@@ -361,17 +364,18 @@ WHERE p1.typsend = p2.oid AND p2.provolatile NOT IN ('i', 's');
 -----+---------+-----+---------
 (0 rows)
 
--- Composites, enums, ranges should all use the same send routines
+-- Composites, enums, multiranges, ranges should all use the same send routines
 SELECT DISTINCT typtype, typsend
 FROM pg_type AS p1
 WHERE p1.typtype not in ('b', 'd', 'p')
 ORDER BY 1;
- typtype |   typsend   
----------+-------------
+ typtype |     typsend     
+---------+-----------------
  c       | record_send
  e       | enum_send
+ m       | multirange_send
  r       | range_send
-(3 rows)
+(4 rows)
 
 -- Domains should have same typsend as their base types
 SELECT p1.oid, p1.typname, p2.oid, p2.typname
@@ -629,3 +633,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.mltrngtypid
+FROM pg_range p1
+WHERE p1.mltrngtypid IS NULL OR p1.mltrngtypid = 0;
+ rngtypid | rngsubtype | mltrngtypid 
+----------+------------+-------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d33a4e143d..5fc0673efa 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -19,8 +19,9 @@ test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeri
 # The second group of parallel tests
 # strings depends on char, varchar and text
 # numerology depends on int2, int4, int8, float4, float8
+# multirangetypes depends on rangetypes
 # ----------
-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 multirangetypes
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index f86f5c5682..98a9afbc7d 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: multirangetypes
 test: pg_lsn
 test: regproc
 test: strings
diff --git a/src/test/regress/sql/hash_func.sql b/src/test/regress/sql/hash_func.sql
index b7ce8b21a3..f8a09a25ff 100644
--- a/src/test/regress/sql/hash_func.sql
+++ b/src/test/regress/sql/hash_func.sql
@@ -220,3 +220,13 @@ FROM   (VALUES (int4range(10, 20)), (int4range(23, 43)),
 		 (int4range(550274, 1550274)), (int4range(1550275, 208112489))) x(v)
 WHERE  hash_range(v)::bit(32) != hash_range_extended(v, 0)::bit(32)
        OR hash_range(v)::bit(32) = hash_range_extended(v, 1)::bit(32);
+
+SELECT v as value, hash_multirange(v)::bit(32) as standard,
+	   hash_multirange_extended(v, 0)::bit(32) as extended0,
+	   hash_multirange_extended(v, 1)::bit(32) as extended1
+FROM   (VALUES ('{[10,20)}'::int4multirange), ('{[23, 43]}'::int4multirange),
+         ('{[5675, 550273)}'::int4multirange),
+		 ('{[550274, 1550274)}'::int4multirange),
+		 ('{[1550275, 208112489)}'::int4multirange)) x(v)
+WHERE  hash_multirange(v)::bit(32) != hash_multirange_extended(v, 0)::bit(32)
+       OR hash_multirange(v)::bit(32) = hash_multirange_extended(v, 1)::bit(32);
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
new file mode 100644
index 0000000000..8651ba3f3d
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,65 @@
+-- Tests for multirange data types.
+
+--
+-- test input parser
+--
+
+-- negative tests; should fail
+select ''::textmultirange;
+select '{,}'::textmultirange;
+select '{[a,c),}'::textmultirange;
+select '{,[a,c)}'::textmultirange;
+select '{-[a,z)}'::textmultirange;
+select '{[a,z) - }'::textmultirange;
+select '{(",a)}'::textmultirange;
+select '{(,,a)}'::textmultirange;
+select '{(),a)}'::textmultirange;
+select '{(a,))}'::textmultirange;
+select '{(],a)}'::textmultirange;
+select '{(a,])}'::textmultirange;
+select '{[z,a]}'::textmultirange;
+
+-- should succeed
+select '{}'::textmultirange;
+select '  {}  '::textmultirange;
+select ' { empty, empty }  '::textmultirange;
+select ' {( " a " " a ", " z " " z " )  }'::textmultirange;
+select '{(,z)}'::textmultirange;
+select '{(a,)}'::textmultirange;
+select '{[,z]}'::textmultirange;
+select '{[a,]}'::textmultirange;
+select '{(,)}'::textmultirange;
+select '{[ , ]}'::textmultirange;
+select '{["",""]}'::textmultirange;
+select '{[",",","]}'::textmultirange;
+select '{["\\","\\"]}'::textmultirange;
+select '{["""","\""]}'::textmultirange;
+select '{(\\,a)}'::textmultirange;
+select '{((,z)}'::textmultirange;
+select '{([,z)}'::textmultirange;
+select '{(!,()}'::textmultirange;
+select '{(!,[)}'::textmultirange;
+select '{[a,a]}'::textmultirange;
+select '{[a,a],[a,b]}'::textmultirange;
+select '{[a,b), [b,e]}'::textmultirange;
+select '{[a,d), [b,f]}'::textmultirange;
+select '{[a,a],[b,b]}'::textmultirange;
+-- without canonicalization, we can't join these:
+select '{[a,a], [b,b]}'::textmultirange;
+-- with canonicalization, we can join these:
+select '{[1,2], [3,4]}'::int4multirange;
+select '{[a,a], [b,b], [c,c]}'::textmultirange;
+select '{[a,d], [b,e]}'::textmultirange;
+select '{[a,d), [d,e)}'::textmultirange;
+-- these are allowed but normalize to empty:
+select '{[a,a)}'::textmultirange;
+select '{(a,a]}'::textmultirange;
+select '{(a,a)}'::textmultirange;
+
+--
+-- test the constructor
+---
+select textmultirange();
+select textmultirange(textrange('a', 'c'));
+select textmultirange(textrange('a', 'c'), textrange('f', 'g'));
+select textmultirange(textrange('a', 'c'), textrange('b', 'd'));
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 624bea46ce..23932cfef0 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -41,6 +41,10 @@ begin
     if (select typtype from pg_catalog.pg_type where oid = $1) = 'r'
     then return true; end if;
   end if;
+  if $2 = 'pg_catalog.anymultirange'::pg_catalog.regtype then
+    if (select typtype from pg_catalog.pg_type where oid = $1) = 'm'
+    then return true; end if;
+  end if;
   return false;
 end
 $$ language plpgsql strict stable;
@@ -164,7 +168,8 @@ WHERE p1.oid < p2.oid AND
 -- need to be modified whenever new pairs of types are made binary-equivalent,
 -- or when new polymorphic built-in functions are added!
 -- Note: ignore aggregate functions here, since they all point to the same
--- dummy built-in function.  Likewise, ignore range constructor functions.
+-- dummy built-in function.  Likewise, ignore range and multirange constructor
+-- functions.
 
 SELECT DISTINCT p1.prorettype, p2.prorettype
 FROM pg_proc AS p1, pg_proc AS p2
@@ -174,6 +179,8 @@ WHERE p1.oid != p2.oid AND
     p1.prokind != 'a' AND p2.prokind != 'a' AND
     p1.prosrc NOT LIKE E'range\\_constructor_' AND
     p2.prosrc NOT LIKE E'range\\_constructor_' AND
+    p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
+    p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
     (p1.prorettype < p2.prorettype)
 ORDER BY 1, 2;
 
@@ -185,6 +192,8 @@ WHERE p1.oid != p2.oid AND
     p1.prokind != 'a' AND p2.prokind != 'a' AND
     p1.prosrc NOT LIKE E'range\\_constructor_' AND
     p2.prosrc NOT LIKE E'range\\_constructor_' AND
+    p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
+    p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
     (p1.proargtypes[0] < p2.proargtypes[0])
 ORDER BY 1, 2;
 
@@ -196,6 +205,8 @@ WHERE p1.oid != p2.oid AND
     p1.prokind != 'a' AND p2.prokind != 'a' AND
     p1.prosrc NOT LIKE E'range\\_constructor_' AND
     p2.prosrc NOT LIKE E'range\\_constructor_' AND
+    p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
+    p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
     (p1.proargtypes[1] < p2.proargtypes[1])
 ORDER BY 1, 2;
 
@@ -273,13 +284,14 @@ SELECT p1.oid, p1.proname
 FROM pg_proc as p1
 WHERE p1.prorettype IN
     ('anyelement'::regtype, 'anyarray'::regtype, 'anynonarray'::regtype,
-     'anyenum'::regtype, 'anyrange'::regtype)
+     'anyenum'::regtype, 'anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
     ('anyelement'::regtype = ANY (p1.proargtypes) OR
      'anyarray'::regtype = ANY (p1.proargtypes) OR
      'anynonarray'::regtype = ANY (p1.proargtypes) OR
      'anyenum'::regtype = ANY (p1.proargtypes) OR
-     'anyrange'::regtype = ANY (p1.proargtypes))
+     'anyrange'::regtype = ANY (p1.proargtypes) OR
+     'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- Look for functions that accept cstring and are neither datatype input
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index fed413bf98..b102c01bbc 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -20,7 +20,7 @@ SELECT p1.oid, p1.typname
 FROM pg_type as p1
 WHERE p1.typnamespace = 0 OR
     (p1.typlen <= 0 AND p1.typlen != -1 AND p1.typlen != -2) OR
-    (p1.typtype not in ('b', 'c', 'd', 'e', 'p', 'r')) OR
+    (p1.typtype not in ('b', 'c', 'd', 'e', 'p', 'r', 'm')) OR
     NOT p1.typisdefined OR
     (p1.typalign not in ('c', 's', 'i', 'd')) OR
     (p1.typstorage not in ('p', 'x', 'e', 'm'));
@@ -151,7 +151,7 @@ SELECT p1.oid, p1.typname, p2.oid, p2.proname
 FROM pg_type AS p1, pg_proc AS p2
 WHERE p1.typinput = p2.oid AND p2.provolatile NOT IN ('i', 's');
 
--- Composites, domains, enums, ranges should all use the same input routines
+-- Composites, domains, enums, multiranges, ranges should all use the same input routines
 SELECT DISTINCT typtype, typinput
 FROM pg_type AS p1
 WHERE p1.typtype not in ('b', 'p')
@@ -180,7 +180,7 @@ SELECT p1.oid, p1.typname, p2.oid, p2.proname
 FROM pg_type AS p1, pg_proc AS p2
 WHERE p1.typoutput = p2.oid AND p2.provolatile NOT IN ('i', 's');
 
--- Composites, enums, ranges should all use the same output routines
+-- Composites, enums, multiranges, ranges should all use the same output routines
 SELECT DISTINCT typtype, typoutput
 FROM pg_type AS p1
 WHERE p1.typtype not in ('b', 'd', 'p')
@@ -232,7 +232,7 @@ SELECT p1.oid, p1.typname, p2.oid, p2.proname
 FROM pg_type AS p1, pg_proc AS p2
 WHERE p1.typreceive = p2.oid AND p2.provolatile NOT IN ('i', 's');
 
--- Composites, domains, enums, ranges should all use the same receive routines
+-- Composites, domains, enums, multiranges, ranges should all use the same receive routines
 SELECT DISTINCT typtype, typreceive
 FROM pg_type AS p1
 WHERE p1.typtype not in ('b', 'p')
@@ -261,7 +261,7 @@ SELECT p1.oid, p1.typname, p2.oid, p2.proname
 FROM pg_type AS p1, pg_proc AS p2
 WHERE p1.typsend = p2.oid AND p2.provolatile NOT IN ('i', 's');
 
--- Composites, enums, ranges should all use the same send routines
+-- Composites, enums, multiranges, ranges should all use the same send routines
 SELECT DISTINCT typtype, typsend
 FROM pg_type AS p1
 WHERE p1.typtype not in ('b', 'd', 'p')
@@ -465,3 +465,9 @@ FROM pg_range p1 JOIN pg_proc p ON p.oid = p1.rngsubdiff
 WHERE pronargs != 2
     OR proargtypes[0] != rngsubtype OR proargtypes[1] != rngsubtype
     OR prorettype != 'pg_catalog.float8'::regtype;
+
+-- every range should have a valid multirange
+
+SELECT p1.rngtypid, p1.rngsubtype, p1.mltrngtypid
+FROM pg_range p1
+WHERE p1.mltrngtypid IS NULL OR p1.mltrngtypid = 0;
-- 
2.20.1

